From React.Component to hooks
Quite recently, React has unveiled its plans for its upcoming 16.7 release. The announce is quite massive as it promotes a new approach that revolves around Stateless Functional Components (SFCs).
To do so, this new version of the library offers what it calls “hooks”, which are tools that allow developers to store state, trigger and handle side effects, and much more inside SFCs.
Although this approach is definitely (in my opinion) an improvement and a great step for React, it may be quite challenging for developers to adopt it as hooks may be hard to understand at first. Hopefully, by the end of this article you’ll have a better understanding of the hooks and how you can take advantage of those.
How hooks works
The biggest thing to understand to get how they work is : Hooks are listed in order by React when it mounts the component, then handled in the same order when it updates it. The order part is crucial here, but what does it mean ?
Let’s say we have a simple component that takes the ID of a user, fetches its profile and greets him using its name:
When React renders this component for the first time (i.e. when the mounting of the component occurs), it registers each call of a React hook successively. Meaning that it does something like this:
- “I am going to create a new state with the initial value
true
“. - “I am going to create a new state with the initial value
null
“. - “I am going to create a new state with the initial value
null
“ (different from the previous one). - “I am going to create an effect and call it immediately”. (this effect will not be created again as long as the props
userId
remains the same).
Now lets say our fetch
function returns some data and call the setLoading
and setUser
functions. These functions will update the first and second states we created and trigger a re-rendering of the component. Which means that we go through the whole code again and React does something different:
- “I have a first state for this component and its value is
false
, i’ll give it back instead of creating a new one“. - “I have a second state for this component and its value is [whatever the api sent back], i’ll give it back instead of creating a new one“.
- “I have a third state for this component and its value is still
false
, i’ll give it back instead of creating a new one“. - “I already registered this effect and the props
userId
didn’t change so I don’t need to call this again”.
By registering all calls to hooks, React is able to store their “state” internally and give us back the informations we need. It can only do so because we respect the two rules of React hooks:
- We cannot call hooks outside of React component (obviously, since they need to be handled by React during the rendering phase, we need to put them in a render function, or in this case an SFC).
- We cannot alter the order and list of hooks. Let’s assume we remove the second
useState
of our component. React sees the first one, gives it back the state it stored for it, sees another one but not the third. It cannot know which one disappeared and will throw an error.
Migrating from lifecycle methods to hooks
From componentDidMount to useMount
The componentDidMount
method is a method that usually triggers side effects, once, when the component is mounted.
The equivalent hook would be something that triggers a side effect when the SFC is called for the first time and stops being called after that. The hook we can use for that is called useEffect
. By default, it is called everytime the component is called (i.e. when mounting and updating). If we take a closer look at the documentation, we see that it can be “memoized” by passing an array of values as the second argument. Moreover, passing an empty array will tell React to call it once and never again. An empty array is like saying “there is no value that would make this function result outdated when it changes”. So our componentDidUpdate
implementation would simply be:
By calling the function with void
, we make sure that we return undefined
to the hook (we will see why it matters when we try to mimic componentWillUnmount
. Since we pass an empty array, React will apply the effect once and never update (as no value is observed by the memoization process). Therefore, it will be called at when the component is mounting.
From componentWillUnmount to useUnmount
The componentWillUnmount
method is also used to trigger side effect, but this time when the component is unmounted. It is not an uncommon case to use it in combination with componentDidMount
, for example to create and delete event listeners.
As we saw in the previous chapter, we can prevent our hook from triggering on every render by passing an empty array of value to observe. By doing so we tell it be called once on mounting then never again. But what if we dont want to call him once at mount-time, but call something at unmount-time ? Well, the documentation from useEffect
tells us that any function returned by the function called by the useEffect
hook will be called when the component is unmounted. So could we could just do something like this:
By calling our arrow function, we do nothing and return the reference to the function we want React to call when it unmounts our component. Once again, passing an empty array ensure we do not repeatedly call our function for every render (since it does nothing anyway, it would be a waste of ressources).
From componentDidUpdate to useUpdate
Last but not least, we would like to mimic the behaviour of componentDidUpdate
, which is to be called on every rendering except the initial one.
We saw in the previous examples that useEffect
takes a second parameter that tells to call the function we pass under certain condition. By not passing this argument, we tell React that ”nothing should ever prevent this effect from being called at render”. That includes the first one, so we need to take care of this one in particular but the rest of the time, React will call our effect regardless of any condition. For the initial rendering, we would need to declare a value (a boolean) that tells us if we’re in the initial rendering or any subsequent one. This value should not trigger and new rendering when updated and should be accessible from our useUpdate
function.
This is a job that can be fulfilled by the useRef
method. Think of useRef
as a useState
that does not trigger a re-rendering of our component. So all we need to do is tweak our useMount
function a little:
In the code above we:
- Create a
ref
using theuseRef
hook. This allows us to store our boolean somewhere we can access between successive calls of our hook. - Always call the effect. This is one of the two rules of hooks. As we explained earlier in this article, React uses the order of the hooks to know what value to store and pass to our code.
- In the arrow function of the
useEffect
hook, we read our previously stored value. If it is true, then we are still in the initial rendering. So we just set it to false so the next time we arrive on the same line of code, our value correctly indicates us that we are not in the initial rendering anymore and we call our function. - We let the second argument to
useEffect
undefined, that way we indicate to React that nothing should prevent the hook from triggering.
From shouldComponentUpdate to memo and useMemo
shouldComponentUpdate
will not be as straight forward as the previous ones. With React 16.7 you can either:
- Use
memo
to memoize a component in regards to its props. Just likeReact.PureComponent
would. - Use
useMemo
to memoize a component’s children.
For more information, the FAQ has a good example.
From getDerivedStateFromProps to… render
Since your state is now located directly in your render function (since your component is the render function), there is no need for a hook to update a state. The FAQ says just enough about that.
From componentDidCatch to… nothing
Sadly, at this point there is no equivalent to componentDidCatch
, so the only solution is to make a classic React.Component
.
Using useLayoutEffect instead of useEffect
As the React documentation specifies, using the useLayoutEffect
hook in our implementations would make our custom hooks closer to the initial behaviour of the React component lifecycle methods. However, it is also mentioned that the useEffect
hook is to be preferred in order to avoid blocking visual updates.
Hence, it is for you to decide whether your should use one or the other. My approach would be to use the useEffect
hook and only use the useLayoutEffect
hook as a fallback when the first one doesn’t behave as expected. For most side effects, like network calls with fetch
for example, the former shouldn’t create any issue.
Hooks by example
This basic tic tac toe game is made using React 16.7 and takes full advantage of the hooks. Don’t hesitate to explore or clone it !
🏴☠️ React Pirate
You can alternatively use react-pirate
(because you know, pirates love hooks 😅), which is a small library of common and useful hooks that I made while redacting this article. The library is still quite young and subjected to multiple evolutions so I’d warmly welcome any feedback and suggestion to make it kind more useful :)
Link to the Github repository: https://github.com/dispix/react-pirate
Edit : As someone mentioned, it is worth noting that what we used to call Stateless Functional Components (SFCs) are not necessarily stateless anymore. So it’s more accurate to refer to them as Function Components (FCs). But keep in mind SFCs is still a term widely use in the community to refer to every React component written as a function and not a class.