React hooks and why should they get you “hooked” ?
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