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);
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);
}
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
}
}, []);
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>;
}
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
);
Instead, do this:
useEffect(() => {
fetchData();
}, []);
return <div>Data loaded</div>;
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>
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 π―
- Every list item has a unique key π
- When calling
setState
, only use the parameter for the previous value βͺ - Objects and arrays are not directly mutated when calling
setState
π - Hooks have neither excessive nor missing dependencies π
- Heavy computations are memoized, and there are no inline objects or callbacks β‘
- All subscriptions are cleaned up on unmount π§Ή
- There are no references to
ref.current
in the render body π« - Avoid side-effects inside JSX β‘
- Ensure proper accessibility with ARIA attributes βΏ
Top comments (4)
Nice detailed article with proper showing of problem and explanation
Thanks a lot, appreciate the kind words! π
Tried to keep it practical and straight to the point. Glad it landed well!
Love the conciseness and value in this post π
Darn, this checklist is like a safety net for React bugs; feels like having a flashlight when youre stuck in a dark cave coding.