How to use callback with useState hook in React?

  1. Approach 1. With useEffect
  2. Approach 2. With useEffect and useRef

Approach 1. With useEffect

With React 16.x and up, if you want to invoke a callback function on state change using useState hook, you can use the useEffect hook to listen to the state change.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { useState, useEffect } from 'react';

export default function App() {
const [count, setCount] = useState(0); // Defaults to 0
useEffect(() => {
console.log('called:', count); // When `count` changes, this function will be called.
}, [count]);
return (
<div className="App">
<p><button onClick={() => setCount(prevCount => prevCount + 1)}>Add</button></p>
<p>Count: {count}</p>
</div>
);
}

Or you can wrap it in a helper function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const useStateCallback = (initilValue, callback) => {
const [state, setState] = useState(initilValue);
useEffect(() => callback(state), [state, callback]);
return [state, setState];
};

export default function App() {
const [count, setCount] = useStateCallback(0, (state) => {
console.log('Count state:', state);
});
return (
<div className="App">
<p><button onClick={() => setCount(prevCount => prevCount + 1)}>Add</button></p>
<p>Count: {count}</p>
</div>
);
}

Note:

  1. The callback function will be called on initial render.
  2. The callback function won’t trigger if the state is identical.

Approach 2. With useEffect and useRef

setState(updater, callback) fro useState

Following implementation comes really close to the original setState callback of classes.

  1. Callback execution is omitted on initial render - we only want to call it on state updates
  2. Callback can be dynamic for each setState invocation, like with classes

Usage

1
2
3
4
5
6
7
8
9
10
11
12
13
const App = () => {
const [state, setState] = useStateCallback(0); // same API as useState

const handleClick = () => {
setState(
prev => prev + 1,
// second argument is callback, `s` being the *updated* state
s => console.log("I am called after setState, state:", s)
);
};

return <button onClick={handleClick}>Increment</button>;
}

useStateCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function useStateCallback(initialState) {
const [state, setState] = useState(initialState);
const cbRef = useRef(null); // init mutable ref container for callbacks

const setStateCallback = useCallback((state, cb) => {
cbRef.current = cb; // store current, passed callback in ref
setState(state);
}, []); // keep object reference stable, exactly like `useState`

useEffect(() => {
// cb.current is `null` on initial render,
// so we only invoke callback on state *updates*
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null; // reset callback after execution
}
}, [state]);

return [state, setStateCallback];
}

Ref:

  1. https://stackoverflow.com/questions/54954091/how-to-use-callback-with-usestate-hook-in-react/56394177