Migrate from componentDidUpdate to React hooks
We have recently updated to React 16.8 after more than 1 year stuck with the old version. The process was surprisingly pleasant, in fact, I accidentally updated our React version to 16.8 and we didn’t notice it for 1 week, nothing broken as we have gradually got rid of all the legacy stuff. Now that we have updated to 16.8, the fun officially begins. I have been a regular guest in the refactory hotel recently and I decided to write down some interesting cases I have encountered during my journey.
Before I write anything, I have to admit that despite all the good things React hooks bring to the table, I still think that it has too much magic behind the scene. For example, useState
looks very simple but the fiber architecture to support it is much more complicated. It reminds me a lot about the time when I approached Ruby on Rails. Its magic can be good or bad depending on the situation.
Anyway, one of the common patterns we have in our code base is to use componentDidUpdate
to reset some state after an API call finishes. For example, in an input field, we want to change it from “editing” state to “static” state after its value has been saved.
See the Pen VwwjwRR by Tan Nguyen (@tanqhnguyen) on CodePen.
In this example, I use simulateFluxFlow
to simulate our Redux flow where we have the typical action and reducer to handle the API call and pass the result or any loading indicator as props to the “container” component.
NameInput
is a pure graphical component which knows nothing about the logic to handle its state. NameInputLogic
as the name implies encapsulates all the state management of NameInput
. In reality, depending on how complicated the logic is, we put them into a higher order component (like NameInputLogic
) or in the reducer. My goal is to refactor NameInputLogic
to use React hooks. The first thing to be refactored is the state, with useState
, we can get rid of React.Component
and instead go for the functional one.
function NameInputLogic(props) {
const [isediting, setIsediting] = useState(false);
const [value, setValue] = useState('');
return <NameInput
value={value}
isediting={isediting}
isLoading={props.isLoading}
onChange={(value) => {
setValue(value);
}}
onEdit={() => {
setIsediting(true);
}}
onSave={() => {
props.submitValue(value);
}}
/>;
}
A lot simpler than before, but… it doesn’t exactly do what we want it to do because the input is still in “editing” mode after we save it.
See the Pen Refactored to use useState by Tan Nguyen (@tanqhnguyen) on CodePen.
Fortunately, there is this FAQ How to get the previous props or state?
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
useRef
is a black box used to store any kind of value in its current
property. And the same ref is returned every time React re-renders the component. When used together with useEffect
, we can record the current value and return the previously stored value.
That solves the first part where we need to get the previous value to compare with the current value. Now we need to have a way to trigger the logic. It’s where useEffect
shines. useEffect
is used to perform side effects, and this is the exact thing we are doing now, side effect!
function NameInputLogic(props) {
const [isediting, setIsediting] = useState(false);
const [value, setValue] = useState('');
const {
isLoading
} = props
const prevIsLoading = usePrevious(isLoading);
useEffect(() => {
if (prevIsLoading && !isLoading) {
setIsediting(false);
}
}, [isLoading]);
//... the rest of the code
}
The 2nd parameter passed to useEffect
tells it to listen to changes happened to the provided “values”. I don’t know yet how they detect that but let’s just trust that React knows. And that’s everything we need to do in order to replace componentDidUpdate
with React hooks. I am pretty sure there are better ways to do it but right now this solution is good enough.
And last but not least, the code. Happy refactoring!
See the Pen Refactor componentDidUpdate by Tan Nguyen (@tanqhnguyen) on CodePen.