Naming things

Consistent naming conventions for variables and functions improve code readability, maintainability, and collaboration. Standardised names can act as self-documenting code, reducing the need for additional comments, and well-named elements help facilitate searching and navigating the codebase.

Also, having rules just means less time wasted chatting about what to call something.

Here are some of the rules I’ve picked up for naming things.

Prefix all booleans with is or has

This indicates the variable type and purpose at a glance, and sets them apart from general data variables.

❌ Bad
const bigNumber = number > 999
const App = () => <Component invalid selected featureOn />
✅ Good
const isBigNumber = number > 999
const App = () => <Component isInvalid isSelected hasFeature />

Prefix all functions that return a boolean with check

This clearly communicates that the function’s purpose is to validate or verify some condition, and helps to quickly denote that the function will return true or false.

❌ Bad
const active = (context) => context.active === 'yes'
const isFull = (context) => context.thing > 0
// Variable must have an awkward name to not shadow the function name.
const isItFull = isFull(foo)
✅ Good
const checkActive = (context) => context.active === 'yes'
const checkFull = (context) => context.thing > 0
// Variable can be named complimentary with the function.
const isFull = checkFull(foo)

Prefix all callbacks with on, suffixed with the event name

This clearly indicates the function’s purpose to handle a specific event, aligning with common event-driven patterns and making the code more intuitive.

❌ Bad
<Component
  handleChange={() => {}}
  clicked={() => {}}
  prevClicked={() => {}}
  fetchData={() => {}}
/>
✅ Good
<Component
  onChange={() => {}}
  onClick={() => {}}
  onPrevClick={() => {}}
  onDataFetchSuccess={() => {}}
/>

Prefix functions passed to callbacks with handle, suffixed by the event name

This makes it clear that this function is used by a callback, and closely links the two.

❌ Bad
const Component = ({ number, onClick }) => {
  const addNumber = () => {
    onClick(number + 1)
  }

  return <SubComponent onClick={addNumber}>
}
✅ Good
const Component = ({ number, onClick }) => {
  const handleClick = () => {
    onClick(number + 1)
  }

  return <SubComponent onClick={handleClick}>
}

Prefix setup-only props with default

When a prop is used for setting-up (on mount) only, and then subsequently cannot be controlled by changing said prop, this is known as a “default” prop. This follows the React pattern of controlled vs uncontrolled components. e.g. an input can be given a value or a defaultValue, respectively.

❌ Bad
<Component open startingValue="foo" isSelected />
✅ Good
<Component defaultOpen defaultValue="foo" defaultSelected />

Use lower-kebab-case for file and folder names

This helps with readability and quickly scanning file trees.

❌ Bad
card header.tsx
pageFooter.tsx
ResultsPerPage.tsx
sort_by_role.tsx
✅ Good
card-header.tsx
page-footer.tsx
results-per-page.tsx
sort-by-role.tsx

Suffix test file names with .test

Whilst .spec is also common, using an arbitrary choice is better for consistency, and a file that contains tests is probably best named appropriately.

❌ Bad
test-page-footer.tsx
results-per-page.spec.tsx
sort-by-role.tests.ts
✅ Good
page-footer.test.tsx
results-per-page.test.tsx
sort-by-role.test.ts

Nest components’ and functions’ files in a folder of the same name

This helps with co-locating other files that are locally important to the main file, like tests, supporting utilities, and sub-components. Do not use barrel (index) files as they are hard to navigate, and can cause performance issues with tooling.

❌ Bad
components/
    page-footer.tsx
    results-per-page/
        index.tsx
        results-per-page.tsx
utilities/
    counter.ts
✅ Good
components/
    page-footer/
        page-footer.tsx
    results-per-page/
        results-per-page.tsx
utilities/
    count-items/
        count-items.ts

Prefer named exports

Where possible, use named exports over default exports. They generally provide better support for autocomplete and refactoring, making it easier to maintain and document your code. It’s also perfectly okay to use a file for multiple exports.

❌ Bad
const doesThing = () => null

export default doesThing
✅ Good
export const doesThing = () => null