Hooks
Hooks are a powerful tool for sharing stateful logic across components, but have some odd non-intuitive properties
Things to know about hooks
Object Identity
The first thing to know about hooks (and React in general) is they only care about object identity when doing change detection. So 1 === 1
, and changing a value from 1
to 1
won't trigger a change. But {foo: 'bar'} !== {foo: 'bar'}
, because the object references are different, even if the contents happen to be the same. So something like
will (perhaps unexpectedly) log on every render loop, because every time React renders, it will run the component function body, create a new object reference in x
, and re-run the useEffect - even though the data (bar
) is the same.
To prevent this, you can use useMemo
to only re-create the object reference only when its underlying data changes, like so
The useEffect
will now only trigger if foo
changes.
This is also the main use case for useCallback
- if you are defining a function in a React component body, and calling that function inside a useEffect, that function must be wrapped in a useCallback
(or extracted outside of the component), or on every render React will see a few object reference for the function and re-run the useEffect. Example:
Types of hooks
Because change detection is so important, there are three primary types of hooks:
Holds state and triggers re-renders: useState/useReducer/zustand
These hooks hold some state, and when updated, will trigger a React render loop for all child components
Performs work on change: useEffect
This doesn't hold state, but simply runs when some state changes
Reduces changes: useRef/useMemo/useCallback
These hooks all exist to help you work around React's change detection mechanism. useMemo/useCallback
both directly skip object changes, and useRef
is for using mutation to never change object references.
When to not useMemo/useState
Once you start using useMemo/useCallback, it can be tempting to use it everywhere - after all, if it skips rendering, why not wrap every calculation in a useMemo? However, this is usually a mistake if done purely for performance. A simple calculation that returns a primitive (like a number or string, which don't trigger change detection if its value doesn't change between renders) is almost always less work than setting up a new function to potentially run and then checking a cache to decide whether or not to run it. So as a rule of thumb, unless you need to prevent a useEffect, just do the calculation on every render. And if you find performance problems on a page, use the React performance profiler before adding useCallback/useMemo.
useState to memoize
If you have a value that should be calculated once, and never again even if other state changes, then instead of useMemo you can use useState
Note that often you can just extract the value into a constant out of the component, but this technique is useful if the value should be unique per component
Last updated