Advanced React Hooks
useReducer
useReducer
We know that useReducer
accepts a reducer and an initial state as its first 2 arguments.
However, you can also pass a 3rd init
callback function that takes the initial state and performs lazy initialization for that initial state.
This is useful for the same reasons as lazy initialization in useState
: to save computational expenditure.
useCallback
useCallback
There are 2 situations where the memoization behind useCallback
is useful:
Improve performance by avoiding expensive computations (like re-rendering expensive components or calling expensive functions)
Value stability
As an example, every time a component re-renders, if it has a nested function inside, that function will have a new reference!
That means the child component will re-render as well, even if nothing changed!
If you want to avoid those unnecessary re-renders, you can memoize handleClick
to add in value stability.
Now handleClick
will always have the same reference (it has value stability), which means MemoizedButton
won't go through unnecessary re-renders.
Pro tip: In a lot of cases, optimizing for these performance improvements aren't worth it. You're introducing complexity for your team for sometimes very minor improvements. Additionally, the memoization can come with a cost.
Value Testability
useContext
useContext
Default value
When you createContext
, you can pass an initial default value. This is useful if someone is trying to access your context outside of the provider, so no value has been provided yet.
Note: It's most likely a mistake to try to access context outside of a provider, but it's good to know anyways.
Composition model
A common use case for context is to avoid prop drilling.
Scoping context
One of the benefits of context providers is that you can choose which parts of the React component tree have access to your context. You don't have to make it globally available. You can choose to limit context to a branch of the tree instead.
useLayoutEffect
useLayoutEffect
useLayoutEffect
differs from useEffect
in that it runs before the browser paints the DOM (whereas useEffect
runs after).
Most of the time you just want useEffect
. But because of their slight difference, there are a few legitimate use cases for useLayoutEffect
:
Your side effect mutates the DOM in a perceivable way.
When you're mutating the DOM, you want to ensure your changes apply before browser paint. Otherwise, it could cause a flicker of content where the change takes effect (which can happen with
useEffect
).
You are interacting with something in the DOM that could change after browser paint, but you need to know its details before browser paint.
One example of this is a
ref
. Maybe you need to know theref.current
value before a re-render happens and changes the value.Another example is a scroll position. Maybe you need to know a DOM node's scroll position before a re-render since that re-render could change the position.
You want to make sure a side effect runs before any other side effect.
Maybe your side effect affects all your other side effects, so you need to make sure it happens first to guarantee that the other side effects run accurately.
useImperativeHandle
useImperativeHandle
Sometimes you want to expose imperative methods inside a component for a parent component to use. To do this, you would use a combination of forwardRef
and useImperativeHandle
.
Note: In most cases, useImperativeHandle
is not the best idea. That's because imperative APIs are just not nearly as good as declarative ones. Only use this hook in situations where it really makes sense.
useDebugValue
useDebugValue
When you make custom hooks, you use useDebugValue
to expose the values you want your custom hook to show in React DevTools.
In the example above, any user using useDebugValue
will see initialCount
and step
when debugging on React DevTools.
Bonus: useDebugValue
accepts an optional formatter function as a second argument. This allow you to format the debug values you pass. (It's only really useful when your formatting is computationally expensive, so you only want to run it when the user actually opens React DevTools and not every time the hook runs.)
Last updated