The initial api is the same as the useState hook from react, with the only difference that the setState function can also take a callback as input. This callback is guaranteed to be called after the state has been updated and the new state is also passed as input.
setState Callback in a Class Component import React, { Component } from 'react'; class App extends Component { constructor(props) { super(props); this. state = { age: 0, }; } // this. checkAge is passed as the callback to setState updateAge = (value) => { this. setState({ age: value}, this.
The useCallback hook is used when you have a component in which the child is rerendering again and again without need. Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
The React hooks equivalent of the setState callback is the useEffect hook. The useEffect hook lets us watch the change of a state.
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]);
If you want to update previous state then you can do like this in hooks:
const [count, setCount] = useState(0);
setCount(previousCount => previousCount + 1);
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])
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);
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.
const [state, setState] = useStateCallback(1);
setState(2, (n) => {
console.log(n) // 2
});
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;
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.
I was running into the same problem, using useEffect in my setup didn't do the trick (I'm updating a parent's state from an array multiple child components and I need to know which component updated the data).
Wrapping setState in a promise allows to trigger an arbitrary action after completion:
import React, {useState} from 'react'
function App() {
const [count, setCount] = useState(0)
function handleClick(){
Promise.resolve()
.then(() => { setCount(count => count+1)})
.then(() => console.log(count))
}
return (
<button onClick= {handleClick}> Increase counter </button>
)
}
export default App;
The following question put me in the right direction: Does React batch state update functions when using hooks?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With