In the previous reading, you learned about dependency arrays in useEffect. Now, let's examine a more complex challenge: dealing with reactive functions inside a component.
Functions defined inside a component are reactive values, meaning a new instance of the function is created every time the component renders.
function App() {
const [num, setNum] = useState(1);
const message = `${num} is ${isEven(num) ? "" : "not "}even`;
function isEven(num) {
return num % 2 === 0;
}
useEffect(() => {
if (isEven(num)) console.log(num);
}, [num, isEven]);
return {message}
;
}
Here, isEven is a function defined inside the component. Since it gets re-created on every render, the dependency array will always detect a change and re-run the effect unnecessarily.
Every time a function is created inside a component, a new function instance is generated, even if its logic is the same.
a = () => console.log('hi');
b = () => console.log('hi');
console.log(a === b); // false
Since a and b are separate instances, comparing them returns false. This is why isEven triggers unnecessary re-renders.
The easiest way to prevent unnecessary re-renders is to move the function outside the component:
function isEven(num) {
return num % 2 === 0;
}
function App() {
const [num, setNum] = useState(1);
const message = `${num} is ${isEven(num) ? "" : "not "}even`;
useEffect(() => {
if (isEven(num)) console.log(num);
}, [num]);
return {message}
;
}
Since isEven is now a stable function, it no longer needs to be in the dependency array.
useCallbackIf moving the function outside the component is not an option, wrap it in useCallback to ensure it does not get recreated on every render:
import { useCallback } from 'react';
function App() {
const [num, setNum] = useState(1);
const isEven = useCallback((num) => num % 2 === 0, []);
useEffect(() => {
if (isEven(num)) console.log(num);
}, [num, isEven]);
return {num} is {isEven(num) ? "" : "not "}even
;
}
useCallback ensures that isEven remains stable unless its dependencies change.
Unlike functions created inside a component, useState setter functions are always stable and do not need to be included in the dependency array.
const [count, setCount] = useState(0);
useEffect(() => {
setCount(prev => prev + 1);
}, []); // No need to include setCount
Some hooks, like useNavigate (React Router) and useDispatch (Redux), return stable functions but must still be included in the dependency array.
import { useDispatch } from "react-redux";
function App() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchData());
}, [dispatch]);
}
In this reading, you explored:
useCallback to stabilize functions.useState setters are stable.To continue learning, visit:
Handling functions inside dependency arrays requires an understanding of function instances in JavaScript. Moving functions outside the component or using useCallback can prevent unnecessary re-renders and improve performance.