When working with react state in a class component, if we wanted to update a
specific property on the state object, all we had to do was call the
setState method and pass in an object
containing only the updated property and value. The resultant state would be
the previous state merged with the new property value.
It works a bit differently with hooks. When using the default
useState hook, the new object passed in
entirely replaces the previous state. So all the properties in the previous
state are overwritten. So every time you want to update a state object, the
onus is on the developer to keep track of the previous state and update the
new state based on that. Here is how this behaviour manifests in code:
As you can see in the code the value of the FirstName property is lost when we update the LastName property.
There is a way to set the state based on previous state by using
the functional update pattern: https://reactjs.org/docs/hooks-reference.html#functional-updates
But that means that every time we used to just use this.setState in class components, we now
have to use
setState(prevState => {
return {...prevState, ...updatedValues};
});
This would be additional overhead for us devs which I wanted to check if we
could avoid.
Fortunately with the reusable nature of hooks, we can simply create a custom
hook which mimics the class components
setState behaviour and merges the new
state with the previous state. And we can use this custom hook any time we
want to merge the state.
Here is how the previous code would look when using our custom hook:
So how does our custom hook work? It's built as a wrapper on top of the
useState hook's functional update pattern. Any time a state object is passed to the
setCustomState function, it internally
uses the functional update pattern and merges the new state with the previous
state. Let's have a look at the code:
This automates the overhead of using the functional pattern. It is no longer
the the developers responsibility, and instead, is done by the custom hook.
But wait, what if there is a scenario where a new state depends on the
previous state? Our custom hook does not yet expose a way for the developer
to update the state based on the previous state. Let's fix that. We can update our hook to add a method which accepts a function. This function will receive the previous
state from react.
Hope that helps! For the sake of completeness here is the full code for our
custom hook:
This post generated some good discussion on twitter with Yannick Plenevaux regarding the ideal cases when to use this approach as opposed to other approaches of state management like using the useState or useReducer hooks. Have a look here: https://twitter.com/yp_code/status/1265244244077416448