React Hooks are a powerful technique liked by many developers. Why? There are so many reasons for that. One of them is that this feature extended the use of functional components. I'd like to talk about the most common hooks and present some tips about using them to improve app performance. So, let's get started.
First off, you should know that hooks are functions. They allow using some React features in function components like state managing or use of lifecycle methods. However, you should keep in mind that they should be called only on the top level, so you can't call a hook inside a conditional statement or loop.
There are only two places where you can call the hook: inside function component or inside another hook. While the first case is rather obvious, the second one is usually related to writing custom hooks which are a good way to share logic between components.
I feel like I should also make a distinction between the hooks before I start writing about them. So, generally, we can divide them into basic and additional ones. To the first group, we rank:
While in the other set, we have:
In this article, I intend to focus on the most common ones, so if you're interested in the rest of them, you should read Hooks API Reference. You'll find a description of all React Hooks, good practices and answers for frequently asked questions.
Let's start with useEffect. This hook accepts 2 arguments - the first is a function, and the second is an array called dependencies.
Dependencies are optional, and the default value is null. The passed function will be executed each time after the component's mount or when some value in the dependencies array changed.
There are also two options in this hook. If you pass an empty array as dependencies, useEffect will be executed once during the first component's render. But, if your function passed to useEffect Hook returns a function, it will be executed during unmount component phase. Just like this:
useEffect(() => {
const subscription = subscribeSomething();
return () => subscription.unsubscribe();
}, [...deps])
Now that you know it let's move on to some good practices of using this hook.
It's used to prevent redeclaring functions in the component's definition. Hence, it won't create new references for them. Using useCallback is similar to useEffect – we have a function and dependencies.
const fn = useCallback(() => {
// fn definition
}, [...deps])
If you don't use useCallback, after component's render fn would be a new reference. There's no problem if it's the first render since a new reference just will be created. But in every next render, the old reference will be cleaned by the garbage collector, and fn would be a new reference.
You may wonder now why we have to useCallback if the old reference will be cleaned anyway. What's the point, right? The answer is that if you put fn in another hook dependencies, it'll be recognized as a change. The result? Using useCallback everywhere to keep references may be nonoptimal.
This hook allows programmers to use a technique called memoization. What's that for? Let's assume you have a function that contains expensive computation. In some cases, it can be called multiple times with the same set of arguments, such as user clicked next, then previous, then next. If we memoize arguments and the result of a particular function call, we don't have to execute a whole function's code. We can just return a result.
But the hook has requirements:
These principles are characteristic of a paradigm called functional programming. Applying useMemo is similar to working with other hooks – you need to pass a function and dependencies. However, you can also memorize a whole component using React.memo. Because function components are functions, they may also be pure functions.
It's a hook for component local state managing. It works very simply, but I would like to consider the below example.
const Component: FunctionComponent<{ id: string }> = ({ id }) => {
const [isFetching, setFetching] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState([]);
useEffect(() => {
setError(null);
setFetching(true);
fetchData()
.then(res => setData(res.data))
.catch(err => setError(err.message))
.finally(() => setFetching(false))
}, [id]);
The main problem is that component will re-render whenever its state or props has been changed. In the example above, we use multiple useState Hooks to manage specific values.
Below is the second approach. We can define a state as a single object and then mutate it.
const Component: FunctionComponent<{ id: string }> = ({ id }) => {
const [state, setState] = useState<State>({
isFetching: false,
data: []
});
useEffect(() => {
setState(prevState => ({
...prevState,
isFetching: true,
error: undefined
}))
fetchData()
.then(res => setState({
data: res.data,
isFetching: false
}))
.catch(err => setState({
error: err.message,
isFetching: false,
data: []
}))
}, [id]);
What do you think? Which approach is better? Should we always use a single object to minimize the number of renders?
In my opinion, both are fine. It really depends on the case. For example, when you're facing some problems with re-renders, I'd try to use a single object. But if you have a simple state and it's easier for you, you can divide it into separate state values. The final decision is yours.
In this case, I just wanted to show that there's no one recipe for writing optimal code and that there's no need to optimize everything in your application.
Also, sometimes optimizations are done too early, which jeopardizes bringing the expected results. That's why I tend to focus on writing clean code and optimize things only when customer needs, wants or when we're facing some visible performance troubles.
I hope you enjoyed this article about using React Hooks and good practices related to it. As you can see, hooks are a very useful technique, which changed stateless function components into "full-fledged" components extending them with lifecycles, element references, and state management. And, of course, they provide the opportunity for performance optimization.
If you're interested in finding out more about the subject, visit our blog. There's plenty of information about React's components, libraries, and many more.
This article was written by Wojciech Hanzel, FullStack Developer at Gorrion.