import { useState, useEffect, useRef } from 'react'; | |
// Usage | |
function App() { | |
// State value and setter for our example | |
const [count, setCount] = useState(0); | |
// Get the previous value (was passed into hook on last render) | |
const prevCount = usePrevious(count); | |
// Display both current and previous count value | |
return ( | |
<div> | |
<h1>Now: {count}, before: {prevCount}</h1> | |
<button onClick={() => setCount(count + 1)}>Increment</button> | |
</div> | |
); | |
} | |
// Hook | |
function usePrevious(value) { | |
// The ref object is a generic container whose current property is mutable ... | |
// ... and can hold any value, similar to an instance property on a class | |
const ref = useRef(); | |
// Store current value in ref | |
useEffect(() => { | |
ref.current = value; | |
}, [value]); // Only re-run if value changes | |
// Return previous value (happens before update in useEffect above) | |
return ref.current; | |
} |
This comment has been minimized.
This comment has been minimized.
Just an optimization, not sooooo inportant useEffect(() => {
ref.current = value;
}, [value]); // only run if and only if value is changed |
This comment has been minimized.
This comment has been minimized.
@ritwickdey Thanks, code updated! |
This comment has been minimized.
This comment has been minimized.
I wrote the following alternative which (even if it looks a bit complicated) can get around with just a single useState and should increase performance a lot because it does not need to check if the effect needs to run on every render. import { useState, Dispatch, SetStateAction } from 'react';
type SetStateFn<T> = (previousValue: T) => T;
type IValueWrapper<T> = [
/** value */
T,
/** setValue */
Dispatch<SetStateAction<T>>,
/** previousValue */
T | undefined
];
/**
* Drop-in replacement for react useState which also returns previousValue.
* @return [value, setValue, previousValue]
*/
export default function useStateWithPrevious<T>(initialValue: T) {
const createSetStateDispatch: (previousValue: T) => Dispatch<SetStateAction<T>> = previousValue => value => {
const newValue = typeof value === 'function' ? (value as SetStateFn<T>)(previousValue) : value;
setValueWrapper([
newValue,
createSetStateDispatch(newValue),
previousValue,
]);
};
const [valueWrapper, setValueWrapper] = useState<IValueWrapper<T>>(() => [
initialValue,
createSetStateDispatch(initialValue),
undefined,
]);
return valueWrapper;
} |
This comment has been minimized.
This comment has been minimized.
Clean up function usePrevious(value) {
// The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class
const ref = useRef();
// Store current value in ref
useEffect(() => {
ref.current = value;
// Clean Up
return () => {
ref.current = null;
}
}, [value]); // Only re-run if value changes (cc @AmaanKulshreshtha ) |
This comment has been minimized.
This comment has been minimized.
The function doesn't quite do what I expected it to do. If another state changes and renders the component again, function usePrevious(value) {
const ref = useRef({ v: value, prev: undefined });
// update `ref` only, if the value really changes,
// the previous value is kept persistent in `prev`,
// so it also contains the previous value for
// further renders with the same `value`.
if (ref.current.v !== value) {
ref.current = {
prev: ref.current.v,
v: value
};
}
return ref.current.prev;
} |
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
Any reason why we need the useEffect? Would this be cheaper?: function usePrevious(value){
const ref = useRef()
const previous = ref.current
ref.current = value
return previous
} |
This comment has been minimized.
This comment has been minimized.
@thebiltheory Why is cleanup necessary? Haven't seen any examples that set a ref value back to null on cleanup and it's not mentioned in https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state @PutziSan That should be handled by the dependency array no? @deadlyicon @PutziSan From what I've read it's not concurrent mode safe to update a ref in the body like that, so could cause bugs in the future when concurrent mode is used. |
This comment has been minimized.
This comment has been minimized.
Good to know. Thanks @gragland |
This comment has been minimized.
This comment has been minimized.
It's a fancy hook, but I'm wondering why is the ref not been initialized at the the 1st rendering. export default function usePrevious(value) {
const ref = useRef(value);
// ...
} Would this be better? |
This comment has been minimized.
This comment has been minimized.
Because technically it had no previous value on the first render and you may need to know that in your components. |
This comment has been minimized.
Love all the comments in the custom hook function!