With the introduction of React Hooks in version 16.8 of React back in 2019, many developers have shifted away from class components in favour of functional components, as state can be introduced and managed within a functional component.
The two most commonly used hooks are
useEffect. Indeed, these tend to satisfy the basic requirements of many components.
useState allows the developer to create a value in state and update that throughout the component.
useEffect allows the developer to execute a function after the rendering of a component is complete (see my other article on this topic here).
However, React Hooks are a lot richer than what is described above, and they include made other less commonly used — but equally valuable — hooks which developers should familiarise themselves with. The benefits that can accrue from their use have the potential to greatly improve the overall performance of an application. The full set of built-in React Hooks is comprehensive and their incorporation to a large-scale application can be of enormous benefit.
A very useful but less widely used hook is
useContext. This hook allows the developer to reference data across multiple components without passing it as a prop to those components. In applications where the creation of a Redux store is regarded as too much of an overhead, given that only a very small amount of data needs to be shared,
useContext is very useful. Prior to the introduction of hooks, React context consisted of both a
Provider and a
Consumer. This worked well, required for nesting within a
Consumer when it was necessary to access a value of that
Consumer. This created extra complexity and overhead. With the
useContext hook, a value can be extracted from a context (which has previously been created with
createContext. Thus, there is no need for a
Consumer any longer and it is much easier to pass data across multiple components.
useReducer allows the developer to manage state in a less trivial manner than
useState. Whilst both seem to be similar conceptually: they allow for state to be created and updated within a component, they differ significantly in practice. With
useReducer, an action is dispatched which is handled by a reducer. It can be used in scenarios where a large amount of state variables are required within a component. Rather than calling the
useState hook multiple times inside of a component, the
useReducer hook can be called once, and multiple state values can be managed inside of a reducer whenever an action has been dispatched. So for a component where state management is that bit more complex,
userReducer can be more efficient than
useCallback is a very useful hook for optimising the performance of an application, where heavy callback functions are passed as props to child components. Because every rendering of a component creates a different instance of a function object, the function is recreated on each re-rendering. Typically, the cost of this is not high. However, when a case exists where the creation of a complex function is high, the recreation of that function with each re-rendering is expensive and negative for performance. The
useCallback function effectively memoizes the callback function, so that it is not recreated on a re-render if the dependency values for the function do not change. This is particularly useful when a component that is contained within
React.memo() accepts a callback function as a prop.
useMemo is a very nice hook which can be used to improve the performance of an application. Whilst it is similar in concept to the
React.memo() API, it is a completely different feature.
React.memo() is used to memoise a component, whereas the
useMemo hook is used to memoise a function. Its benefit can be seen when applied to an expensive function which is executed with every render of a component. If the dependencies of the function do not change from one render to the next, the re-execution of the function does not change the outcome, and so this overhead is an unwanted cost in performance. By passing a function into
useMemo, it will only re-execute if the dependencies of that function differ from its dependencies during the previous render.
useRef hook is a convenient means to keep track of the mutable value of a DOM element. A change in the value of such a reference does not result in any re-render of the component and so it is an inexpensive way to monitor the values of such elements throughout the life of the component. The value of the component referenced is stored in the
.current property of the DOM node and it updates as the value mutates.
useImperativeHandle hook is a useful hook for exposing aspects of a component to its parent component (which references the said component with the
useRef hook). So for example, if the parent component is required to access a property or function of the referenced child component, then the
useImperativeHandle hook should be called inside of the referenced component. The property or function which is to be exposed to the referencing parent component should be passed as an argument to the hook.
useLayoutEffect hook is very similar to the
useEffect hook. Its only difference is when its effect is executed. In the case of
useEffect, its effect is executed asynchronously after a DOM has rendered its mutations. This is opposed to
useLayoutEffect, which fires synchronously to the rendering of the DOM and after the mutations have completed.
useDebugValue hook is a useful way to add labels to custom hooks inside of React DevTools. It results in an easier and quicker way to see the values of custom hooks without having to click to expand them inside of DevTools.
The full suite of React Hooks, as outlined above, provide a comprehensive set of tools to boost application performance effectively. They will not always be very beneficial: an application with inexpensive function creations or executions will not be greatly enhanced by some of these hooks. However, for large-scale applications with expensive overheads, these hooks can act as a means to significantly optimise performance and enable excellent UX, especially when used across multiple instances in an application.