I have a child component that does a fetch
in a useEffect
then calls a passed in onChange
handler. The child component gets re-rendered 3 times (and does the fetch
3 times) all because the parent’s onChange
function "changes".
Requiring the parent to wrap onChange
in a useCallback
would work but seems wrong as the parent shouldn’t have to care about how the child is implemented. I could also get the child to cache onChange
(maybe with useRef
or a custom hook like usePrevious
) and check in the useEffect
if it has changed, but this also seems a little over the top.
Is there a best practice for this sort of pattern? (Ideally without requiring redux or similar)
import { useEffect, useState } from 'react';
const Child = ({ onChange, value }) => {
useEffect(() => {
console.log("called");
fetch(`blah/${value}`)
.then((result) => onChange(result))
.catch((error) => onChange(error));
}, [value, onChange]);
return <div>Child</div>;
};
export default function Parent () {
const [result, setResult] = useState();
return (
<div>
{result}
<Child
value={0}
onChange={r => {
setResult(r.toString());
}}
/>
</div>
);
}
2
Answers
If you want to track value of something, you have to memoize its reference somehow. useCallback, useRef, useState, useMemo… Since on each render functions get new reference and that triggers things that tracks those functions.
The parent does not care how the children will use it, it just memoizes the callback for general purpose.
This isn’t about a parent component caring about how a child component uses a passed prop or any of its implementation, it’s about providing a stable reference to consumers and not triggering unnecessary rerenders. In other words, it’s a performance enhancement.
React components rerender for a couple reasons, either their state or props update (e.g. via new references), or the parent component rerenders.
You are passing an anonymous callback function as the
onChange
prop, so any time this parent component renders, it’s providing a newonChange
function reference and will certainly trigger a child rerender.Yes, memoize the callback that is passed down so it’s provided as a stable reference.
Memoize an
onChange
handler function in order to provide a stable function reference to the child component so it only rerenders to the DOM when thevalue
prop updates.So now React can tell that the
onChange
reference doesn’t change render to render, and so long as thevalue
prop doesn’t update, React can bail on child component (and its entire sub-ReactTree) rerenders even though the parent component rerendered. Again, this is just a performance optimization to cut down on unnecessary rerenders.See the useCallback hook documentation for further details and other reasons to memoize function references.