-
Notifications
You must be signed in to change notification settings - Fork 49.7k
Description
Do you want to request a feature or report a bug?
More of a question / discussion as I could not find it in the documentation.
What is the current behavior?
Currently when using hooks like useState and useReducer, any updates to props used by their initial state has no effect on the state managed by these hooks. For instance, consider this example from the Hooks API Reference:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
Now let's suppose that a parent component initially renders a Counter with initialCount set to 0. But later on the initialCount prop passed by the parent component is updated to let's say 11. I would expect that a change in the initialCount prop, would then trigger a change in the count state but that doesn't happen.
At first I wasn't sure why the count was not updating as I expect any data changes to flow through where they are used. It seems like that the initial state is only used initially as the name suggests in useState or useReducer and any changes to it are ignored by these hooks.
However, we are still able to address the problem by using effects:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
useEffect(() => setCount(initialCount), [initialCount]);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
The need to use effects wasn't immediate to me but it makes sense as we would generally hook such behavior into component lifecycle methods like componentDidUpdate. I am curious as to why this doesn't already happen automatically through useState and useReducer, and wondering if this is the correct way to listen to such prop changes and accordingly update state, or is there some better way?
We could also extract this logic into a custom hook:
function useStateWithEffect(initialState) {
const [state, setState] = useState(initialState);
useEffect(() => setState(initialState), [initialState]);
return [state, setState]
}
// Haven't tested the reducer but as an idea
function useReducerWithEffect(reducer, initialArg, init = x => x) {
const reducerWithStateUpdate = (state, action) => action.type === 'updateInitialArg' ? init(action.initialArg) : reducer(state, action);
const [state, dispatch] = useReducer(reducerWithStateUpdate, initialArg, init);
useEffect(() => dispatch({ type: 'updateInitialArg', initialArg }), [initialArg]);
return [state, dispatch]
}
I spent some time trying to figure this out so if someone has reference to material already discussing this problem, please do share as I couldn't find it while going through the docs / posts about hooks.