Skip to main content

React Best Practices

Best practices.

Checklist

  1. Avoid hardcoded values (constants, magic numbers) in components. Define them in a constants file.
  2. Use a consistent and logical folder structure for your project.
  3. Create components for reusability and organization, even if they're only used once.
  4. Avoid unnecessary markup (divs) and use React fragments when possible.
  5. Don't add layout styles to reusable components. Use wrapper elements or className props instead.
  6. Use TypeScript for better type checking and developer experience.
  7. Keep components simple and "dumb". Lift complex logic up the component tree.
  8. Use the children pattern to avoid prop drilling.
  9. When naming props for functions, follow conventions like "onEventName" (e.g., onClick, onSubmit).
  10. Use useMemo, useCallback, and React.memo for performance optimization when necessary.
  11. Use the updater function form of useState when new state depends on previous state.
  12. Consider using a single state instead of multiple states for related data (e.g., using a status enum instead of separate loading/error booleans).
  13. Keep one source of truth. For selected items, store IDs rather than entire objects.
  14. Use the URL for some state (like filters or pagination) instead of useState.
  15. Keep useEffect simple: one concern per useEffect.
  16. Instead of fetching data in useEffect, consider alternatives like React Query or SWR for better caching and performance.
  17. Improve overall structure with components, custom hooks, and utility functions:

Components

  • Use components for reusable markup
  • Use utility functions for reusable logic
  • Use custom hooks for reusable logic that includes React hooks

Best Practices for React component design

  • Always name your React components
  • Write consistently organized components
  • Separate constants and helpers into different files
  • Destructure props
  • Don't pass a ton of props in
  • Avoid nesting render functions
  • Conditional rendering practices
  • Assign default props when destructuring

Data Strategy

Pass or Fetch?: The decision to pass data to a component via props or to have the component fetch the data directly from a server can depend on a number of factors.

Passing Data

Passing Data as Prop.

Pros:

  1. Separation of Concerns: This approach keeps data-fetching separate from your component logic. Your component's sole concern becomes presentation, not data management.
  2. Reusability: The component can be more easily reused in different contexts, as it doesn't depend on a specific API endpoint or data source. Any parent component can pass data to it.
  3. Easier Testing: It's easier to test components that have their data passed in as props because you can provide specific data as props for testing scenarios.

Cons:

  1. Prop Drilling: If the component hierarchy is deep, passing data down through many layers of components (known as "prop drilling") can become messy and hard to manage.
  2. Efficiency: It might not always be efficient. If only a specific component needs data, getting it at a higher level means that data will be fetched even when the component isn't rendered.

Fetching Data

Fetching Data Inside the Component

Pros:

  1. Encapsulation: The component is self-contained and manages its own state and data fetching logic. This can lead to cleaner parent components and a more modular codebase.
  2. Efficiency: If only a specific component needs certain data, fetching it inside the component ensures that data is only fetched when needed.

Cons:

  1. Reusability: If the data fetching is tied to a specific API endpoint or data structure, the component might become less reusable.
  2. Testing: Components that fetch their own data can be more difficult to test, as they require a way to mock the data fetching for different scenarios.

In general, if the data is used by multiple components or throughout the application, it might be more efficient to fetch the data higher up in the component tree (e.g., in a container component or using a global state management tool like Redux, MobX or the Context API) and then pass it down as props.

On the other hand, if the data is specific to a single component and isn't used elsewhere, fetching it within the component itself can be a good option.

Ultimately, the best approach can depend on the specific requirements of your project, your team's preferences, and the complexity of your application.

Components as Props

React's props model is very flexible, allowing you to pass various types of data, including JSX and entire components.

TypeScript commonly helps to ensure type safety with:

  • React.ReactNode
  • React.ComponentType
  • React.ElementType

Choosing the right type depends on your specific use case, but keep performance implications in mind.

Actions

  1. Identify the Prop Type: First, ascertain what type of prop you're dealing with (JSX, component, native tag, etc.).
  2. Type Accordingly: Use the appropriate TypeScript type depending on what you're passing (e.g., React.ReactNode, React.ComponentType, React.ElementType).
  3. Use Advanced Typing for Flexibility: If your application requires the ability to pass any component and infer its props, be prepared to dive deep into advanced TypeScript.
  4. Performance: When possible, prefer passing JSX as it doesn't re-render when the parent component re-renders, providing a performance advantage.

Heuristics

Passing JSX as a Prop: Use React.ReactNode to type props that receive JSX.

  • Passing a Component as a Prop: Use React.ComponentType to type props that receive entire components.
  • Passing Native Tag or Component: Use React.ElementType to type props that receive either a native HTML tag or a component.
  • Passing Any Component and Inferring Its Props: This is more complex and requires advanced TypeScript.

totaltypescript.com

Typescript

Best practices

Merging Libraries

Checklist for designing React components with multiple types of props when integrating with libraries.

  1. Identify Conflicting Props
  2. Create Omit Types
  3. Define Your Component's Props
  4. Extract Props in Component
  5. Apply Props to Elements
  6. Optional: Provide Default Values

Identify Conflicting Props

Check for property names that are common between the library's props (like MotionProps in your case) and standard HTML props.

Create Omit Types

Use TypeScript's Omit utility to create types that exclude these conflicting properties.

type SomePropsWithoutConflicts = Omit<SomeLibraryProps, 'conflictingProp1' | 'conflictingProp2'>;

Define Your Component's Props

Define your component's own prop types based on what it needs, including any special behavior.

interface MyComponentProps {
customProp?: boolean;
libraryProps?: SomePropsWithoutConflicts;
htmlProps?: Omit<HTMLProps<HTMLElement>, 'conflictingProp1' | 'conflictingProp2'>;
}

Extract Props in Component

In your component, extract these props and any other specific props you need.

const MyComponent: FC<MyComponentProps> = ({ customProp, libraryProps, htmlProps }) => {
// ...
};

Apply Props to Elements

Apply these props to the corresponding elements within your component. Use the spread syntax to apply bulk props.

<div {...htmlProps}>
<LibraryComponent {...libraryProps}>{/* content */}</LibraryComponent>
</div>

Optional: Provide Default Values

If your component relies on certain default prop values, define these in your component using default parameters or other methods.

const MyComponent: FC<MyComponentProps> = ({
customProp = true,
libraryProps = {},
htmlProps = {},
}) => {
// ...
};

Test the Component

Make sure to test the component thoroughly to ensure that both the library and standard HTML props are being applied and overridden as expected.

Review and Refactor

Review the code for readability and maintainability. Refactor if necessary, keeping future changes and debugging in mind.

By following these steps, you can ensure that your component remains customizable and maintainable, while also integrating smoothly with any third-party libraries.

Antipatterns

Things to avoid in React

  • useEffect
  • Redux everywhere
  • Single CSS strategy
  • Super Huge Components