Borislav Hadzhiev
Mon May 02 2022·2 min read
Photo by Ashim D’Silva
To update state when props change in React:
useEffect
hook.useEffect
is reran.import {useEffect, useState} from 'react'; function Child({parentCount}) { const [childCount, setChildCount] = useState(0); useEffect(() => { setChildCount(parentCount * 2); console.log('useEffect logic ran'); }, [parentCount]); // 👈️ add props as dependencies return ( <div> <button>Child count {childCount}</button> </div> ); } export default function Parent() { const [parentCount, setParentCount] = useState(0); return ( <div> <button onClick={() => setParentCount(current => current + 1)}> Parent count: {parentCount} </button> <hr /> <Child parentCount={parentCount} /> </div> ); }
We used the useEffect hook to update the state of a component when its props change.
useEffect(() => { setChildCount(parentCount * 2); console.log('useEffect logic ran'); }, [parentCount]); // 👈️ add props as dependencies
The logic in the useEffect
hook is reran every time one of its dependencies
changes.
Every time the parentCount
prop changes, the useEffect
hook is reran and we
use the setChildCount
function to update the state.
useEffect
hook.Note that the function we passed to useEffect
hook is also invoked on the
initial render.
If you don't want to run the logic in your useEffect
hook on the initial
render, but only when the specific prop changes, use a ref
to return early on
the initial render.
import {useEffect, useRef, useState} from 'react'; function Child({parentCount}) { const [childCount, setChildCount] = useState(0); const isFirstRender = useRef(true); useEffect(() => { if (isFirstRender.current) { isFirstRender.current = false; return; // 👈️ return early if first render } setChildCount(parentCount * 2); console.log('useEffect logic ran'); }, [parentCount]); return ( <div> <button>Child count {childCount}</button> </div> ); } export default function Parent() { const [parentCount, setParentCount] = useState(0); return ( <div> <button onClick={() => setParentCount(current => current + 1)}> Parent count: {parentCount} </button> <hr /> <Child parentCount={parentCount} /> </div> ); }
We used a ref to exit
early when the useEffect
hook is ran on mount.
Use this approach if you want to listen for props changes but need to skip the first render.
Here is an example that demonstrates this issue.
import {useEffect, useState} from 'react'; function Child({parentCount, setParentCount}) { useEffect(() => { // 👇️ this causes infinite loop setParentCount(current => current + 1); console.log('useEffect logic ran'); }, [parentCount, setParentCount]); // 👈️ parentCount is a dependency return ( <div> <button>Parent count {parentCount}</button> </div> ); } export default function Parent() { const [parentCount, setParentCount] = useState(0); return ( <div> <Child setParentCount={setParentCount} parentCount={parentCount} /> </div> ); }
The issue is that we added the parentCount
prop to the hook's dependencies
array but we are also updating its value in the hook.
Every time useEffect
is ran, the value of parentCount
is changed, which
re-runs the hook again because parentCount
is in its dependencies array.