Blog ― React Hooks Explained – Part Two
Introduction
We have recently gone through React Hooks in the first part of this series, which transformed the React development patterns and the generic ecosystem. We think of hooks as functions that allow React developers to use life-cycle methods and manage the state within functional components. Hooks offer developers to get the power to use functional components for almost any need, from rendering the UI component to handling the state and the component logic. Let's remind ourselves three React Hooks core principles.
- Make sure to not use Hooks inside loops, conditions, or nested functions;
- Only use Hooks at the top level of React Functional Component;
- Always start a Hook with the name of "use", because you are using the super powers of the React library
Make sure you have familiarized yourself and feel confident with the detailed hooks description from Part One before you move on! We will go through some really scary ones this time (just kidding, this will be fun).
useReducer
This hook is often referred to as one of the scariest, while in reality, it is just a conceptually different state management way. The state management logic is a different concern that should be managed in a separate place.
React provides the useReducer
hook to separate the concerns (rendering and state management). The hook does so by extracting the state management out of the component. And yes, it is that simple!
This hook returns an array of two values just like the useState
hook, where the first value is the reactive state that you want to show in the UI.
However, the second value is where things get different. Now you need the function that can dispatch the action instead of the state update how it was with the useState
hook. Heads up! An action is just an object that has the type, which can be any string you want and an optional data payload.
You might dispatch an action when the button is clicked, which will trigger your reducer function. The reducer function is something you define and then pass as an argument to the useReducer
hook. The function takes the current state and the action as arguments, and uses these two values to compute the next state which is usually handled inside the switch statement. Lastly, the hook takes the initial state as it's second argument.
Despite being able to use the useReducer
Hook to handle complex state logic in our app, it’s important to note that there are certainly some scenarios where a third-party state management library like Redux may be a better option. Generally, as was stated before, this hook provides more predictable state transitions than useState
, which becomes more important when state changes become so complex that you want to have one place to manage state, like the render function.
As your application grows in size, you’ll most likely deal with more complex state transitions, at which point you’ll be better off using useReducer
. And yes, this scary hooks is that simple!
useImperativeHandle
The useImperativeHandle
Hook customizes the instance value that is exposed to parent components when using ref
. As always, imperative code using refs should be avoided in most cases. The Hook is similar to but it lets you do two things:
- It gives you control over the value that is returned. Instead of returning the instance element, you explicitly state what the return value will be (see snippet below).
- It allows you to replace native functions (such as blur, focus, etc) with functions of your own, thus allowing side-effects to the normal behaviour or a different behaviour altogether. Though, you can call the function whatever you like.
Imagine you build a reusable component library in React, you may need to get access to the underlying DOM element and then forward it, so it can be accessed by the consumers of your component library. You can access a native DOM element with the useRef
hook that we came across earlier, then you can wrap the component in forwardRef
(forward reference to the element) to make the ref
available when someone uses this component.
When you need a bidirectional data and logic flow, but you don’t want to overcomplicate things by introducing state management libraries, the useImperativeHandle
Hook is a great choice.
For example, I once used the useImperativeHandle
Hook when I needed to open a modal component when a button in the parent component was clicked.
Defining state in the parent component would cause the parent component and its children to re-render each time the modal button was clicked, therefore, I wanted the state in the child component. Instead, I stored the modal state in the Modal
component using useImperativeHandle
and forwardRef
.
useLayoutEffect
Do you remember the useEffect
Hook, which also happens to be one of the most confusing for junior developers to catch up with? The useLayoutEffect
Hook works just like the useEffect
Hook since their signatures are identical, but it fires synchronously after all DOM mutations.
The above means the code will run after rendering the component but before the actual updates have been painted on the screen. Now React will wait for your code to finish running before it updates the UI.
This can be useful if you need to make DOM measurements (like getting the scroll position or other styles for an element) and then make DOM mutations or trigger a synchronous re-render by updating the state.
Heads up! Use the useLayoutEffect
Hook if you need to mutate the DOM and/or do need to perform measurements, or try useEffect
if you don't need to interact with the DOM at all or your DOM changes are unobservable (seriously, most of the time you should use this).
useDebugValue
Not that many developers are aware of the useDebugValue
hook, although it enables a much better debugging experience when it comes to debugging custom hooks.
useDebugValue
is a simple inbuilt hook which provides extra information about the custom hook internal logic within the React DevTools. It allows you to display supplementary, practical information next to your custom hooks, with optional formatting. This is incredibly useful if you are creating a library for others to use since they can easily see information about your hook, but it also is useful for your own hooks since you can quickly see detailed information about your hooks.
The hook extends the visualization of data about custom hooks inside of the Component Inspector of the React DevTools. Consider installing the React DevTools Browser extension if you do not have one yet!
In this custom useUser
hook we have a useDebugValue
hook that takes a string as its only argument. Passing a single string to useDebugValue
is by far the most common way to use this hook and when you do you will see something interesting in the React dev tools if you click on a component that utilizes this hook.
On the right side of the screen you will see a section labeled hooks
and inside the section you will see the User
hook and next to that you will see the debug information we passed to useDebugValue
.
Custom React Hook
Custom React hook is always a new composition of one or multiple hooks. If a custom hook does not use any hooks internally, it's not a custom hook and shouldn't have the prefix "use".
Our first step in creating the custom React hook will be the custom hook function definition in its own file, much like a React component since a custom hook is like a component, but it stores logic instead of presentation. This means we will have a file called useLocalStorage.js
.
There is a useState
at the beginning which will query localStorage
to get the value if it already exists, but if not then the initialValue
will be set for the state. Then useEffect
is used to update localStorage
every time the value is updated.
The reason the function version of useState
is used is because it will check the localStorage
first before setting the state to the initialValue
. If useEffect
was used to check localStorage
instead like below it would cause the component to render twice. Once when the initialValue
was set and once after the useEffect
.
Based on the use case above we know that the useLocalStorage
hook will need a key
to store the state as well as an initialValue
to set the state to if there is nothing in localStorage
. We can thus update our code to look like this.
Then in the component where localStorage
is being used the code can be simplified to just one simple line which has the same return values as useState
since the useLocalStorage
hook will behave exactly like useState
.
Now all that is left to do is move over the logic for how to handle localState
into the useLocalStorage
hook and return value
and setValue
.
As you can see the majority of this code is the same as the previous localStorage
code, but there are two main differences. The first is that the key is no longer a const
since it is a parameter to the useLocalStorage
hook so that needs to be set as a dependency for useEffect
. Second, the value
and setValue
variables are being returned from the hook in the exact same format as useState
so this hook can be used in the exact same way as useState
.
Summary
That is all there is to creating custom hooks in React. They are no more than just fancy functions that can use React hooks inside of them, but they are incredibly powerful in cleaning up code and sharing logic between components!
Magebit is a full service eCommerce agency specialized in Magento. At Magebit we create the wonders of eCommerce and support small sites as well as large enterprises.
You can contact us at info@magebit.com or through the contact us page.
Subscribe to our blog
Get fresh content about eCommerce delivered automatically each time we publish.