How to use `setState` callback on react hooks
React hooks introduces useState
for setting component state. But how can I use hooks to replace the callback like below code:
setState(
{ name: "Michael" },
() => console.log(this.state)
);
I want to do something after the state is updated.
I know I can use useEffect
to do the extra things but I have to check the state previous value which requires a bit code. I am looking for a simple solution which can be used with useState
hook.
Solution 1:
You need to use useEffect
hook to achieve this.
const [counter, setCounter] = useState(0);
const doSomething = () => {
setCounter(123);
}
useEffect(() => {
console.log('Do something after counter has changed', counter);
}, [counter]);
Solution 2:
If you want to update previous state then you can do like this in hooks:
const [count, setCount] = useState(0);
setCount(previousCount => previousCount + 1);
Solution 3:
Mimic setState
callback with useEffect
, only firing on state updates (not initial state):
const [state, setState] = useState({ name: "Michael" })
const isFirstRender = useRef(true)
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false // toggle flag after first render/mounting
return;
}
console.log(state) // do something after state has updated
}, [state])
Custom Hook useEffectUpdate
function useEffectUpdate(callback) {
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false; // toggle flag after first render/mounting
return;
}
callback(); // performing action after state has updated
}, [callback]);
}
// client usage, given some state dep
const cb = useCallback(() => { console.log(state) }, [state]); // memoize callback
useEffectUpdate(cb);
Solution 4:
I Think, using useEffect
is not an intuitive way.
I created a wrapper for this. In this custom hook, you can transmit your callback to setState
parameter instead of useState
parameter.
I just created Typescript version. So if you need to use this in Javascript, just remove some type notation from code.
Usage
const [state, setState] = useStateCallback(1);
setState(2, (n) => {
console.log(n) // 2
});
Declaration
import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react';
type Callback<T> = (value?: T) => void;
type DispatchWithCallback<T> = (value: T, callback?: Callback<T>) => void;
function useStateCallback<T>(initialState: T | (() => T)): [T, DispatchWithCallback<SetStateAction<T>>] {
const [state, _setState] = useState(initialState);
const callbackRef = useRef<Callback<T>>();
const isFirstCallbackCall = useRef<boolean>(true);
const setState = useCallback((setStateAction: SetStateAction<T>, callback?: Callback<T>): void => {
callbackRef.current = callback;
_setState(setStateAction);
}, []);
useEffect(() => {
if (isFirstCallbackCall.current) {
isFirstCallbackCall.current = false;
return;
}
callbackRef.current?.(state);
}, [state]);
return [state, setState];
}
export default useStateCallback;
Drawback
If the passed arrow function references a variable outer function, then it will capture current value not a value after the state is updated. In the above usage example, console.log(state) will print 1 not 2.