React hooks and why should they get you “hooked” ?

Mindaugas Nakrosis
4 min readApr 17, 2021

--

You must have heard about React hooks by now. They let you use state and other React features without writing a class.

Hooks offer a powerful and expressive new way to reuse functionality between components. “Building Your Own Hooks” provides a glimpse of what’s possible. This article by a React core team member dives deeper into the new capabilities unlocked by Hooks.

But why classes are “bad”?

Well it has these problems:

1. Wrapper hell

2. Huge components

3. Confusing classes

As you have many different lifecycle events, constructors, bindings (to this reference).

Let’s try out various hooks while comparing classes to hooks.

useState

Here we have a Counter component in a class with conventional state management.

The same can be written as a functional component with the help of hookuseState. There is no more need of constructor, bindings, classes.

useState “Gotchas.”

  • When we call the function to update the state, it queues a re-render of the component.
  • You can have multiple state variables with useState
  • On the contrary of the previous setState, useState replaces state objects instead of merging them.
  • If you are using an object as state, when you are setting state, you can spread the state to avoid losing properties.

useEffect

As we can see this component has lifecycle events.

This example perfectly illustrates how class components can become big and confusing. We can get rid of different lifecycle events by using useEffect. It triggers side effects when dependency value changes that is provided into the array (second argument). So in our code snippet every time text value changes. SideEffect document.text = text gets called. Let’s take a look at an example.

Looking at the second effect we see that the dependencies array is empty — that means that that effect will only be run once (given that it doesn’t have any dependencies to watch). The second thing is return — this return is what is called a cleanup. It will be run when our effect is being cleaned up (similar to componentWillUnmount).

So we:

  • On render subscribeToStore
  • On component unmount unsubscribeFromStore

With useEffect, you can couple related logic, making the code more readable and more comfortable to debug and do several effects according to our needs.

useEffect “Gotchas.”

  • If no dependencies array is passed the effect will run every re-render
  • You can return a function from our effect — it will be doing the clean up.
  • If an empty dependencies array is passed we aren’t watching any changes so the effect will only run on the first render.
  • Try avoiding objects in the dependencies array as if they aren’t memoized then the references will change on every re-render forcing that effect to run unexpectedly.

useContext

Context is used to pass data throughout the component data tree without drilling the props through each child. It works by declaring a context provider at the top-level and a context consumer where the context needs to be read and used. Let’s look at an example.

It looks straightforward at first but when you need multiple contexts the component tree can start becoming hard to read and understand.

Using useContext we can achieve the same result with hooks.

Easy to access the context, no wrapper hell!

useContext “Gotchas.”

  • Component that is subscribed to the changes of context re-renders when any of the context values change (as you would expect).

Some more less used hooks

useRef

Using useRef we can access DOM directly.
It creates a mutable reference on its .current property given an initial value. With useRef you can store JavaScript objects to guarantee that you receive the same object on every render.

useMemo

This hook is used for computationally expensive operations. Let’s say we have a huge array of objects. And every time that array of object changes we want to recalculate something in each of the objects. This is computationally expensive and should not be done on every re-render. That’s where useMemo comes in. It only gets called when dependencies passed into the array change.

We should have in mind that useMemo will be called every re-render if the reference to an array or object (dependency) changes on each render. So if we move ourbaseketBallTeam variable declaration inside our functional component, it will get recomputed on each render.

useCallback

Similar to the point I mentioned above (in the useMemo). If we create a function every time that component renders, then it creates a new reference for it.

If we are passing that function as a dependency to React.memo it will be treated as new one each time it gets created in the render. We do not want that, right?

useCallback creates a function and re-creates it every time anything in the dependencies array change (same as useEffect). In our case, we leave the dependency array empty as we do not have any variables in the setCounter and it will create a stable — reference to the increment prop that we are passing to MemoizedButton

Rules of Hooks

Call Hooks only at the top level

Hooks shouldn’t be called from inside conditions, loops, or nested functions. That is because React relies on the order in which the Hooks are called, and if you skip a Hook update, then that order would be changed and cause unexpected behaviour.

A Hook name must start with “use”

This is standard that is also used in various linters to detect hooks

A Hook must be called from a function component or from another Hook

Hooks may only be called from React function components or custom Hooks.

Thanks for reading!

If you want to read more of my articles, I publish every week.
Twitter: https://twitter.com/MindaugasNakroi

--

--

No responses yet