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:

My simple user-greeting component

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 the useRef 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 like React.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 !

https://codesandbox.io/s/github/dispix/react-hooks-example

🏴☠️ 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.