Blog ― React Hooks Explained – Part One
React 16.8 with additional API features was officially released in 2019, which has become a monumental update to the classical class-based environment. This React version introduced Hooks which transformed the React development patterns and the generic ecosystem.
React Hooks are opt-in, which means that rewriting existing code is unnecessary, so they do not contain any breaking changes, and they’re available for use with the release of React 16.8. Some developers have already been making use of the Hooks API as of a sense of curiosity even before it was released officially, but back then, it was not stable and reliable and was only an experimental feature. Nowadays, this feature is a part of React API and is recommended for React developers to use.
Why React Hooks?
We think of React Hooks as functions that allow React developers to use lifecycle methods and manage the state within functional components. The way Hooks were introduced to the public was that they allow developers to use state in functional components but under the hood, Hooks are much more powerful than that. They allow to enjoy the following benefits:
- Significantly improve code reuse;
- Suitable code composition;
- Better defaults;
- Sharing non-visual logic with the use of custom hooks;
- Flexibility in moving up and down the
components
tree.
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.
It is totally fine to keep using classes in React, but Hooks generally provide a more ergonomic way to approach components because you can reuse stateful logic without changing the component's hierarchy. We are actually big fans of React Hooks here at Magebit, and we utilise them when developing our projects as often as possible.
ReactJS official documentation defines the following motivation behind the release of React Hooks:
- Reusing stateful logic between components can be challenging – Hooks allow reusing logic between components without changing their architecture or structure.
- Complex components are always difficult to follow and understand – Big projects like our Magento 2 eCommerce storefront will significantly grow and become bigger since they carry out many operations. This will make components difficult to track in the long run. Hooks solve this by allowing to separate a particular single component into various smaller functions based upon what pieces of this separated component are related (such as setting up a subscription or fetching data), rather than having to force a split based on lifecycle methods.
- Some developers may find Classes quite confusing – Classes are a barrier to learning React since junior developers need to understand how
this
in JavaScript works, which R-E-A-L-L-Y differs from other languages. React Hooks solves this problem by allowing developers to use the best of React features without the need to use classes.
React Hooks Rules
Two main rules are strict to be adhered to as stated by the React core team, which they outlined in the hooks proposal documentation.
- Make sure to not use Hooks inside loops, conditions, or nested functions;
- Only use Hooks at the top level of React Functional Component;
- And one extra: Always start a Hook with the name of "use", because you are using the super powers of the React library
React has 10 built-in Hooks, and you will understand what every single one of them does throughout this article series. In addition, you will learn how to build your Hooks from scratch in the upcoming Part Two article to extract your component logic into reusable functions.
useState
This is one of the most important, and often used Hook. The purpose of useState
is to handle the state inside functional components without needing to convert it to a class component. Heads-up! React updates the UI each time the state
changes, so the latest snapshot is reflected to the end-user.
The Hooks takes one optional argument, which is the initial state. The function returns the array, that contains two values you can use within your component.
The reason it is returned in an array is because we can destructure them with JavaScript to easily assign the values to local variables that we can name whatever we want. The first value is the reactive data (or the state). If we then reference it in the UI, and its value changes in the future, React will automatically rebuild the UI to show the lates value. The second value in the array is the setter function. For example, we might listen to the onClick event on the button and when clicked, we will call the setCount function to increment the count by one.
useEffect
This is the second important Hook, which also happens to be one of the most confusing for junior developers to catch up. It is essential to understand React component lifecycle to comprehend how the useEffect
Hook works.
The component is first added to the UI, which can only happen once, then the component's reactive data can change, or in other words, be updated, which can occur multiple times. Finally, at some point, the component will be removed from the UI (unmounted).
React components generally utilise props and state to calculate the output. If the functional component performs calculations that don't target the output value, these calculations are referred to as side-effects. Fetch requests, DOM direct manipulations, timer functions like setTimeout
, and more are the side-effects examples.
The useEffect
Hook allows implementing all three component lifecycle events from within a single function API. It takes a user-defined function that contains effectual code. React will then run your function (a side-effect) after it has updated the DOM. The following useEffect
implementation will run any time the stateful data changes on the component.
This means it will run when the component is initialised with the default value, then run again each time the state is updated.
Heads-up! In functional components, effects like mutations, subscriptions, time tracking, logging, and others should not exist inside the function's body because doing so would lead to inconsistencies during the UI render.
In most cases, you would want more control over that behaviour. Let's imagine we want to fetch data when the component is initialised and then update the state asynchronously after the data has been fetched. The above code will result in an infinite loop because every time we do the fetch, we update the state, which then triggers another fetch, and so on forever.
Introducing the second useEffect
argument, which is an array of dependencies, is the way to address this issue. Empty array means no dependencies, which means it will only run once, when the component is first initialised. In other cases we might want to re-run the function any time certain stateful data has changed. This can be achieved by adding the state to the dependencies array.
Now React will track that value and re-run the function any time it changes. We might also want to run some code, when the component is destroyed. The way we implement the teardown function, is through returning a function from the useEffect
callback.
React will call this function when the component is destroyed.
useContext
This Hook allows interaction with React Context API, which itself is a mechanism that allows to share, or scope values throughout the entire component tree. Heads-up! Context shares the data without a need to pass props.
Let's imagine we have a fruits
object, and we want to share the current fruit across multiple disconnected components. For this, we can safely create a context. One part of the application might require banana, so we use the Context Provider to scope the value.
Now any child component can inherit that value without the need to pass props down to the children. The useContext
Hook finally allows us to consume the current value from the Context Provider, which might be defined many levels higher in the component tree. Bringing a parent value with useContext
is much easier than tracking props down through multiple children.
Now, if the fruit changes from banana to apple in a parent provider, the children's component value will be updated automatically.
useRef
The useRef
Hook is a function that returns a mutable ref object, which keeps reference between renders. The returned object will also be available for the full lifetime of the component.
It can be used when you have a value that changes like in the setState
Hook, but the difference is that it doesn't trigger re-render when the value changes. Heads-up! Mutable value does NOT re-render the UI.
The most common use for the useRef
hook is to access HTML elements from the DOM (access native HTML elements from JSX). From there, we can call native DOM APIs like click, access the current value, and much more! The bottom line is when you need to access the element from the DOM, useRef
is the Hook you are looking for as it "does not go against" the React Virtual DOM convention.
useMemo
The general purpose of the useMemo
hook is to optimise computation costs to improve performance. This Hook should ONLY be used for expensive calculations (no, we are not talking about money here).
It is good to consider the useMemo
hook as the opt-in tool to deal with expensive computations that you know are likely to impact the performance. While useMemo
can improve the performance of the component, you have to make sure to profile the component with and without the hook. Only after that make the conclusion whether memoization worth it.
When memoization is used inappropriately, it could harm the performance.
Let's imagine we have a counter, but then we want to calculate the additional expensive property. Instead of recomputing on every render, React can memorise the value through initialising the function, that returns the computed value, we then pass the second argument that contains dependencies, which determine when this computation should run (recalculate when the count
changes).
This is considered good practice for memorising return values, but we might also want to memorise the entire function, in which case we are looking for a useCallback
Hook.
useCallback
In short, React's useCallback
hook is used to wrap functions. This Hook instructs React to not re-create a wrapped function when a component re-renders, unless any of the dependencies change.
It does so by memoizing the callback function that this hook takes and this memoized version will only change when one of the inputs has changed. This is useful when passing callbacks to optimised child components that rely on reference equality to prevent unnecessary renders.
What the useCallback
does is that it internally caches the first created version of the function and returns it to the caller if the listed dependencies haven’t changed. In case the listed dependencies does change, then it automatically gives us a new instance of that function.
I hope this article helped you understand the basic, though most used React Hooks. You will learn how to build your Hooks from scratch in the upcoming Part Two article.
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.