Improve Your Application Performance With React Hooks

Written by gorrion | Published 2021/03/22
Tech Story Tags: react | reactjs | react-hook | javascript | react-hooks | programming | software-development | application-performance

TLDRvia the TL;DR App

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.

The basics

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.

The variety

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:
  • useState
  • useEffect
  • useContext
While in the other set, we have:
  • useCallback
  • useMemo
  • useReducer
  • useRef
  • useImperativeHandle
  • useLayoutEffect
  • useDebugValue
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.

useEffect

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])

How to apply useEffect 

Now that you know it let's move on to some good practices of using this hook. 
  • If you want to do some action only once after the component renders, you should pass the empty array as dependencies. It's a good place for data fetching, subscribing, and many more.
  • Remember to release memory and clean after useEffect execution when the component unmounts. It's a good practice that will also ensure no memory leaks.
  • If a function called inside of useEffect uses another variable, that variable should be included in dependencies, and these dependencies should contain only values from the component's scope.
  • Define functions that aren't executed anywhere else inside useEffect. Thanks to this, you don't have to pass function reference into dependencies.

useCallback

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.

useMemo

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:
  • The function must be predictable. It means the result must be the same for a certain set of arguments. For example we know passing a = 2, b = 2 for function (a, b) => a + b will always return 4.
  • The function cannot create side effects. It has to do the computation without modifying anything anywhere in the application.
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.

useState

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>({
        isFetchingfalse,
        data: []
    });

    useEffect(() => {
        setState(prevState => ({
            ...prevState,
            isFetchingtrue,
            errorundefined
        }))

        fetchData()
            .then(res => setState({
                data: res.data,
                isFetchingfalse
            }))
            .catch(err => setState({
                error: err.message,
                isFetchingfalse,
                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.

Final thoughts

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.

Written by gorrion | We provide custom software development for industries such as healthcare, fitness, edu-tech, marketing and IoT.
Published by HackerNoon on 2021/03/22