I am looking for a way to perform more advanced comparison instead of the second parameter of the useEffect
React hook.
Specifically, I am looking for something more like this:
useEffect(
() => doSomething(),
[myInstance],
(prev, curr) => { /* compare prev[0].value with curr[0].value */ }
);
Is there anything I missed from the React docs about this or is there any way of implementing such a hook on top of what already exists, please?
If there is a way to implement this, this is how it would work: the second parameter is an array of dependencies, just like the useEffect
hook coming from React, and the third is a callback with two parameters: the array of dependencies at the previous render, and the array of dependencies at the current render.
In class based components
was easy to perform a deep comparison
. componentDidUpdate
provides a snapshot
of previous props
and previous state
componentDidUpdate(prevProps, prevState, snapshot){
if(prevProps.foo !== props.foo){ /* ... */ }
}
The problem is useEffect
it's not exactly like componentDidUpdate
. Consider the following
useEffect(() =>{
/* action() */
},[props])
The only thing you can assert about the current props
when action()
gets called is that it changed (shallow comparison asserts to false
). You cannot have a snapshot of prevProps
cause hooks
are like regular functions, there aren't part of a lifecycle (and don't have an instance) which ensures synchronicity (and inject arguments). Actually the only thing ensuring hooks value integrity is the order of execution.
Alternatives to usePrevious
Before updating check if the values are equal
const Component = props =>{
const [foo, setFoo] = useState('bar')
const updateFoo = val => foo === val ? null : setFoo(val)
}
This can be useful in some situations when you need to ensure an effect
to run only once(not useful in your use case).
useMemo
:
If you want to perform comparison to prevent unecessary render
calls (shoudComponentUpdate
), then useMemo
is the way to go
export React.useMemo(Component, (prev, next) => true)
But when you need to get access to the previous
value inside an already running effect there is no alternatives left. Cause if you already are inside useEffect
it means that the dependency
it's already updated (current render).
Why usePrevious
works
useRef
isn't just for refs
, it's a very straightforward way to mutate
values without triggering a re render. So the cycle is the following
Component
gets mountedusePrevious
stores the inital value inside current
props
changes triggering a re render inside Component
useEffect
is calledusePrevious
is calledNotice that the usePrevious
is always called after the useEffect
(remember, order matters!). So everytime you are inside an useEffect
the current
value of useRef
will always be one render behind.
const usePrevious = value =>{
const ref = useRef()
useEffect(() => ref.current = value,[value])
}
const Component = props =>{
const { A } = props
useEffect(() =>{
console.log('runs first')
},[A])
//updates after the effect to store the current value (which will be the previous on next render
const previous = usePrevious(props)
}