A Deeper Dive Into React Hooks
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 useState
and 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.
useContext
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 theuseContext
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
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 useState
.
useCallback
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
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
The 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
The 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
The 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
The 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.