DEV Community

Joodi
Joodi

Posted on

6 2 2 2 1

React Checklist: 9 Tips to Avoid Bugs 🐞

Image description
Today, I'll be sharing a checklist for reviewing React applications.

Take a quick look at it before committing your changes β€” this will boost your confidence and help you catch those tricky errors.

We'll start by going through each point in detail, and at the end, you'll find the complete list without comments for future reference.

1️⃣ Every list item has a unique key πŸ”‘

Keys allow React to track changes in list items during rendering. Missing or unstable keys can cause unpredictable behavior during re-renders.

Make sure each element has a unique key that remains consistent across renders.

It's best to use an element's ID from the database or any other unique value.

Avoid using the array index as a key unless your array is static, as it doesn't prevent unnecessary re-renders.

2️⃣ When calling setState, only use the parameter for the previous value ⚠️

If state updates depend on the current value, avoid directly using the old value:

const [count, setCount] = React.useState(0);

// Direct update ❌
setCount(count + 1);

// Use the parameterized value βœ…
setCount(prevCount => prevCount + 1);
Enter fullscreen mode Exit fullscreen mode

The setState function in React is asynchronous for performance optimization.

When setState is called multiple times in quick succession, using the functional syntax ensures each update is based on the most recent state.


3️⃣ Objects and arrays are not directly mutated when calling setState 🚫

When updating state, always create a new copy of the data rather than directly mutating the original array or object.

Methods like .push(), .pop(), or mutating objects can cause unpredictable bugs.

const [items, setItems] = React.useState([]);

function addItem(newItem) {
  items.push(newItem); // Mutation
  setItems(items);
}
Enter fullscreen mode Exit fullscreen mode

If the reference to the object doesn't change, React might not trigger a re-render.

Always use methods that return a new copy of the original data, or manually create a copy before updating the state.


4️⃣ Hooks have neither excessive nor missing dependencies ⚠️

If dependencies are missing, your hook may not have the relevant data when it runs, and in the case of useEffect, it may not be triggered at all.

On the other hand, excessive dependencies cause unnecessary re-renders and, in the worst case, can lead to an infinite loop.

Make sure to include all variables, functions, and memoized values used inside the hook in the dependency array.

Global variables or constants don't need to be listed.

To help you manage this, ESLint has a rule called react-hooks/exhaustive-deps β€” it will ensure you list the dependencies correctly. Avoid disabling this rule unless you have a solid reason.

The worst case is completely omitting the dependency array, which can cause unpredictable behavior.


5️⃣ Heavy computations are memoized, and there are no inline objects or callbacks 🧠

This is the "silent killer" of performance.

Any objects or functions placed directly in JSX will be recreated on every render.

This can cause unnecessary re-renders of child components, as the reference is different from the previous render.

To prevent this, move the logic outside of JSX.

If the computation is intensive or the function relies on multiple parameters, make sure to wrap it in useMemo or useCallback to memoize it and improve performance.


6️⃣ All subscriptions are cleaned up on unmount 🧹

The application might continue running code even after the component has unmounted, leading to memory leaks, errors, and unpredictable behavior.

If you use setTimeout, setInterval, addEventListener, or make asynchronous requests or subscriptions inside useEffect, make sure to clean them up when the component unmounts.

useEffect(() => {
  const id = setInterval(...);

  return () => {
    clearInterval(id); // Clean up
  }
}, []);
Enter fullscreen mode Exit fullscreen mode

In modern browsers, you can also use AbortController to cancel API requests when the component unmounts.


7️⃣ There are no references to ref.current in the render body 🚫

ref.current is not initialized during the first render and will be null. The element will only be mounted after the first render pass.

Accessing it directly within the component body can cause errors and unpredictable behavior.

Instead, use useEffect or useLayoutEffect:

function MyComponent() {
  const divRef = useRef(null);
  const [container, setContainer] = useState(null);

  useEffect(() => {
    setContainer(divRef.current);
  }, []);

  useEffect(() => {
    if (container) {
      // Your logic here
    }
  }, [container]);

  return <div ref={divRef}>Example</div>;
}
Enter fullscreen mode Exit fullscreen mode

By the time useEffect is triggered, divRef will be properly initialized.


8️⃣ Avoid side-effects inside JSX ⚑

Side-effects, such as API calls or modifying state, should never be executed inside JSX. This is because JSX is meant for rendering only, and putting logic inside JSX can lead to unexpected behaviors.

Instead, handle side-effects in useEffect or other hooks, and keep JSX purely for rendering.

Example of incorrect usage:

return (
  <div>{fetchData()}</div> // Avoid this! Side-effect inside JSX
);
Enter fullscreen mode Exit fullscreen mode

Instead, do this:

useEffect(() => {
  fetchData();
}, []);
return <div>Data loaded</div>;
Enter fullscreen mode Exit fullscreen mode

9️⃣ Ensure proper accessibility with ARIA attributes β™Ώ

Accessibility should always be a priority in React applications. Make sure to use appropriate ARIA attributes for interactive elements like buttons, links, and form controls.

<button aria-label="Close" onClick={close}>X</button>
Enter fullscreen mode Exit fullscreen mode

This ensures that screen readers and assistive technologies can properly interpret the component and provide the necessary information to users with disabilities.


Recap: The Complete List 🎯

  1. Every list item has a unique key πŸ”‘
  2. When calling setState, only use the parameter for the previous value βͺ
  3. Objects and arrays are not directly mutated when calling setState πŸ›‘
  4. Hooks have neither excessive nor missing dependencies πŸ”„
  5. Heavy computations are memoized, and there are no inline objects or callbacks ⚑
  6. All subscriptions are cleaned up on unmount 🧹
  7. There are no references to ref.current in the render body 🚫
  8. Avoid side-effects inside JSX ⚑
  9. Ensure proper accessibility with ARIA attributes β™Ώ

Top comments (4)

Collapse
 
himanshu_code profile image
Himanshu Sorathiya β€’

Nice detailed article with proper showing of problem and explanation

Collapse
 
joodi profile image
Joodi β€’

Thanks a lot, appreciate the kind words! πŸ™Œ

Tried to keep it practical and straight to the point. Glad it landed well!

Collapse
 
_ndeyefatoudiop profile image
Ndeye Fatou Diop β€’

Love the conciseness and value in this post πŸ™

Collapse
 
nevodavid profile image
Nevo David β€’

Darn, this checklist is like a safety net for React bugs; feels like having a flashlight when youre stuck in a dark cave coding.