Migration from class-based components to functional components seem to be the trend among organizations. I have put together select topics which cover few atypical concepts in React and how hooks amplify these features.
Higher-order components (HoC)
This is still relevant as HoC has been used extensively in several codebases.
Modern implementation of HoC’s involves hooks
and renderProps
.
Consider the following example, in the App functional component, the backgroundColor
is either red or an empty string depending on the boolean property of props.hovered
. This value is returned from our HoC detectHover
.
When exporting the App component in export default detectHover(App)
, we’re also exporting what is getting returned from our HoC.
HoC can be defined as a function that receives a component as an argument and returns a newly augmented component.
The goal for why we’re doing this is to presumably have this logic detectHover
in more than one place throughout the app.
Higher-order components are a way to share logic throughout the app without having to re-write the logic over and over.
RenderProps
Similar to HoC, renderProps
are still extensively used in codebases and because companies don’t have the luxury of moving over to hooks.
The render function inside the App component is being called by <DetectHover render={render} />
DetectHover
is a component that will effectively accept a prop called render
to which you need to pass a function that will render your JSX.
We are invoking the render
function by passing hovered
as an argument to the actual function.
Again as a means to re-use logic across our application using the renderProp
approach.
Hooks
Hooks allow hooking into React class components’ lifecycle methods within a functional component.
React documentation mentions that with HoC
and renderProps
, it can result in “wrapperHell”.
Hooks allow extrapolating logic into a separate function without having to create another wrapper.
We have a custom hook termed useHover
. We attach the hoverRef
to the div.
Within the useEffect
we grab the node out of ref.current
and ultimately return the value determining if it is getting hovered or not getting hovered.
useEffect
useEffect simulates the following class component’s lifecycle methods,
1. ComponentDidMount
The useEffect
dependency array is an empty array. This will run only the first time.
2. ComponentWillUnmount
The cleanup function inside return
under useEffect
.
3. ComponentDidUpdate
In this case, useEffect
contains an argument in the dependency array. In the below example, every time the value of number
changes, the useEffect will be run.
Memo
React.memo
makes a component only render if the props that it's accepting are in fact changing. Even if my parent component changes but if the incoming props to my component are not changing, I don't want to render again.
In the above example, the Child component doesn't receive any props but is still re-rendered on state change.
To overcome this issue, we import memo
from React and simply wrap the child component with the memo
function.
For class-based, we use the shouldComponentUpdate
lifecycle method or alternatively a pure component. For functional components, we use Memo
.
This might be a rather small example where such optimizations don’t mean much but it's the contrary for large-scale applications.
React API’s:
useCallback
In the below example, the Child component takes a function someFunc
as a prop. Despite the fact that the Child component is wrapped in a memo and presumably the prop that is passed to it is not changing (because it's just a function), it's still rendering over and over again. Right?
However, that is not true as the argument which is being passed to the Child component is in fact changing. Every time the App component re-renders a new function is getting created which is passed to the Child component. Even though the function is equivalent and doing the same thing, it is a brand new function in memory. Thus Memo thinks a new prop is getting passed in and begins to re-render.
useCallback
takes 2 arguments, the function you want to memoize
and a dependency array.
useMemo
As useCallback
memoizes functions, useMemo
memoizes values. Consider the below example, where the maximum value is to be returned from a really large array.
This is a very contract example but the point highlighted is that there is a series of inefficiency within our code here because we have a function that is doing expensive work repeatedly even though the outcome is going to be the same because the input is the same.
Implementing useMemo
solves this issue. The last time the function ran, it gave us the number 9. So every time the function is run, again and again, it gives the number 9 without having to recompute the function again.
There should be another article emphasizing the compilation of React concepts in the near future. I’d recommend keeping an eye out for them.