6 October, 2020
Hooks have become a part of React relatively recently, but they already have a major impact on how developers solve various problems with state management and more. But to take full advantage of React hooks requires experience. But while there may be some truth to the saying “no pain, no gain”, it doesn’t mean that you have to be the one to suffer. Through my own experience with hooks, I was able to come up with a lot of React hooks best practices so that you can get it right from the start.
Getting rid of the classes made a lot of the boilerplate code disappear. For example, there is no longer a need to use the constructor and call super with props every time. There is also no need to bind methods or think about what this is at the moment.
When using hooks, it is worth remembering a few rules, so that the code works as expected and is understandable to others. In this article, I will tackle two of them – useState and useEffect.
React hooks best practices – basics
I hope you already know this one, but I must mention it: Hooks cannot be called conditionally. You cannot put them in loops or nested functions inside your component.
So what to do if you want to call a hook conditionally? Just put the condition inside your hook.
Why is this so important? Hooks can be called multiple times in a given component and React operations are based on the order in which they are called. Not following this would introduce bugs to your application and fatal errors.
Fortunately, there is an eslint plugin created by React team which enforces you to follow this rule. It is called eslint-plugin-react-hooks and you can find it on npm. If you use Create React App for your application, it is installed by default.
This plugin will work not only with React hooks but also with every custom hook. But for this to happen, you have to follow a specific convention – the hook name must start with use.
Pitfalls with the useState React hook
As I mentioned before, when using React hooks (and generally in programming), it is worth sticking to well-known conventions. When naming values from useState, it is assumed that the elements are called:
By doing so, you will be able to easily find functions linked to the state they update and your code will be consistent across the application.
The useState hook is great for storing the state for simple values such as booleans or form inputs. Unlike the state from classes, you are not limited to a single object. With useState you can (and you should) create separate values for each case.
For example, when working with form fields, instead of keeping user info in one state like this:
It’s better to create separate fields for each case:
Of course, there is no strict limit here and useState can indeed store objects, but there is a thing you need to remember. In class components, when you update state using this.setState, it is merged with the old one. With useState, the update function replaces the old state with a new one; there is no merging at the end. Take a look at this example:
If you updated the state like this, you would lose all your previous data. Don’t worry, there is a way to handle it. Similar to the this.setState class, instead of just passing new values, you can pass a function that receives the previous state. So you could do something like this:
Or you could do it with the spread operator:
One more important thing to note here. When you are updating values based on the previous state, you shouldn’t do it that way:
Do you know where the problem is? State updates work asynchronously, so you should never rely on the previous values in this manner. Instead, you should always use a function, even when not dealing with objects.
useEffect – one React hook to rule them all
The useEffect hook allowed for logical separation of functionalities in the component, depending on what they do, instead of when they should occur. In the class, we had to keep unrelated functionalities in given lifecycle methods. With useEffect, we can split our code into smaller chunks. And just like with useState, you should not worry about calling useEffect several times for each case.
When working with useEffect, in most cases you want to include a dependencies list. Why? Let’s say you want to create a timer in your application. You call the setInterval function inside useEffect to update the timer variable every 1000 ms.
See also: React component lifecycle with hooks
By default, useEffect runs on every render. So when you update your component state inside useEffect, state update triggers re-render. Then, useEffect runs again, which updates the state and it goes infinitely. Or at least until your browser or API crashes. Luckily for us, the React team thought about it and added an eslint rule, which reminds one of this. It is called exhaustive-deps and it is included with eslint-plugin-react-hooks.
But in this case, dependency with a timer would not help, because you would still call setInterval over and over again after the timer variable is updated. Of course, you could use setTimeout instead of setInterval, but there are 2 other things that can be done with useEffect – to pass an empty array as a dependency or use a cleanup function.
If you pass an empty array as a dependency, it will tell React that this effect doesn’t depend on any value, so it will never run again.
Another thing you can do is to use a cleanup function, which is similar to componentWillUnmount from the class component. Everything which is returned from function passed to useEffect will be called when the component is removed. Moreover, if a component is rendered multiple times like in that case, the last effect is clean before the next is executed. Here you could simply return clearInterval with the id of setInterval.
React hooks best practices – summary
That’s it for this little React hooks tutorial. There is a lot more to cover about this topic, but I hope you found some useful tips for these two React hooks examples – useState and useEffect.
As you can see, hooks allow us to significantly simplify components in our application. However, when using them, there are some gotcha parts that need to be considered. If you follow these guidelines, your app will be less prone to bugs and also more understandable for other developers.