I am using functional component with hooks. I need to update state in parent from a child. I am using a prop function in Parent. All works fine except my prop function is getting the previous state and not the current state. My prop function gets executed before useState hook setting current state. How can I can I wait for my call back function to be executed after useState call. I am looking for something like setState(state,callback) from class based components.
Here is the code snippet:
function Parent() {
const [Name, setName] = useState("");
getChildChange = getChildChange.bind(this);
function getChildChange(value) {
setName(value);
}
return <div> {Name} :
<Child getChildChange={getChildChange} ></Child>
</div>
}
function Child(props) {
const [Name, setName] = useState("");
handleChange = handleChange.bind(this);
function handleChange(ele) {
setName(ele.target.value);
props.getChildChange(collectState());
}
function collectState() {
return Name;
}
return (<div>
<input onChange={handleChange} value={Name}></input>
</div>);
}
This question is related to
reactjs
react-hooks
You can use useEffect/useLayoutEffect to achieve this:
const SomeComponent = () => {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
if (count > 1) {
document.title = 'Threshold of over 1 reached.';
} else {
document.title = 'No threshold reached.';
}
}, [count]);
return (
<div>
<p>{count}</p>
<button type="button" onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
More about it over here.
If you are looking for an out of the box solution, check out this custom hook that works like useState but accepts as second parameter a callback function:
// npm install use-state-with-callback
import useStateWithCallback from 'use-state-with-callback';
const SomeOtherComponent = () => {
const [count, setCount] = useStateWithCallback(0, count => {
if (count > 1) {
document.title = 'Threshold of over 1 reached.';
} else {
document.title = 'No threshold reached.';
}
});
return (
<div>
<p>{count}</p>
<button type="button" onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
you can utilize useCallback hook to do this.
function Parent() {
const [name, setName] = useState("");
const getChildChange = useCallback( (updatedName) => {
setName(updatedName);
}, []);
return <div> {name} :
<Child getChildChange={getChildChange} ></Child>
</div>
}
function Child(props) {
const [name, setName] = useState("");
function handleChange(ele) {
setName(ele.target.value);
props.getChildChange(ele.target.value);
}
function collectState() {
return name;
}
return (<div>
<input onChange={handleChange} value={name}></input>
</div>);
}
Another way to achieve this:
const [Name, setName] = useState({val:"", callback: null});_x000D_
React.useEffect(()=>{_x000D_
console.log(Name)_x000D_
const {callback} = Name;_x000D_
callback && callback();_x000D_
}, [Name]);_x000D_
setName({val:'foo', callback: ()=>setName({val: 'then bar'})})
_x000D_
You can use like below -
this.setState(() => ({ subChartType1: value }), () => this.props.dispatch(setChartData(null)));
function Parent() {_x000D_
const [Name, setName] = useState("");_x000D_
getChildChange = getChildChange.bind(this);_x000D_
function getChildChange(value) {_x000D_
setName(value);_x000D_
}_x000D_
_x000D_
return <div> {Name} :_x000D_
<Child getChildChange={getChildChange} ></Child>_x000D_
</div>_x000D_
}_x000D_
_x000D_
function Child(props) {_x000D_
const [Name, setName] = useState("");_x000D_
handleChange = handleChange.bind(this);_x000D_
collectState = collectState.bind(this);_x000D_
_x000D_
function handleChange(ele) {_x000D_
setName(ele.target.value);_x000D_
}_x000D_
_x000D_
function collectState() {_x000D_
return Name;_x000D_
}_x000D_
_x000D_
useEffect(() => {_x000D_
props.getChildChange(collectState());_x000D_
});_x000D_
_x000D_
return (<div>_x000D_
<input onChange={handleChange} value={Name}></input>_x000D_
</div>);_x000D_
}
_x000D_
useEffect
act as componentDidMount, componentDidUpdate, so after updating state it will work
With React16.x, if you want to invoke a callback function on state change using useState
hook, you can use the useEffect
hook attached to the state change.
import React, { useEffect } from 'react';
useEffect(() => {
props.getChildChange(name); // using camelCase for variable name is recommended.
}, [name]); // this will call getChildChange when ever name changes.
Actually, you should avoid using this
when using react hooks. It causes side effects. That's why react team create react hooks
.
If you remove codes that tries to bind this
, you can just simply pass setName
of Parent
to Child
and call it in handleChange
. Cleaner code!
function Parent() {
const [Name, setName] = useState("");
return <div> {Name} :
<Child setName={setName} ></Child>
</div>
}
function Child(props) {
const [Name, setName] = useState("");
function handleChange(ele) {
setName(ele.target.value);
props.setName(ele.target.value);
}
return (<div>
<input onChange={handleChange} value={Name}></input>
</div>);
}
Moreover, you don't have to create two copies of Name
(one in Parent
and the other one in Child
). Stick to "Single Source of Truth" principle, Child
doesn't have to own the state Name
but receive it from Parent
. Cleanerer node!
function Parent() {
const [Name, setName] = useState("");
return <div> {Name} :
<Child setName={setName} Name={Name}></Child>
</div>
}
function Child(props) {
function handleChange(ele) {
props.setName(ele.target.value);
}
return (<div>
<input onChange={handleChange} value={props.Name}></input>
</div>);
}
we can write customise function which will call the callBack function if any changes in the state
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const useStateCallbackWrapper = (initilValue, callBack) => {
const [state, setState] = useState(initilValue);
useEffect(() => callBack(state), [state]);
return [state, setState];
};
const callBack = state => {
console.log("---------------", state);
};
function App() {
const [count, setCount] = useStateCallbackWrapper(0, callBack);
return (
<div className="App">
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
`
setState(updater, callback)
for useState
Following implementation comes really close to the original setState
callback from classes.
Additions made to Robin's solution:
setState
invocation, like with classesconst App = () => {
const [state, setState] = useStateCallback(0); // same API as useState + setState with cb
const handleClick = () => {
setState(
prev => prev + 1,
// 2nd argument is callback , `s` is *updated* state
s => console.log("I am called after setState, state:", s)
);
};
return <button onClick={handleClick}>Increment</button>;
}
useStateCallback
function useStateCallback(initialState) {
const [state, setState] = useState(initialState);
const cbRef = useRef(null); // mutable ref to store current callback
const setStateCallback = useCallback((state, cb) => {
cbRef.current = cb; // store passed callback to ref
setState(state);
}, []);
useEffect(() => {
// cb.current is `null` on initial render, so we only execute cb on state *updates*
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null; // reset callback after execution
}
}, [state]);
return [state, setStateCallback];
}
Further info: React Hooks FAQ: Is there something like instance variables?
const App = () => {
const [state, setState] = useStateCallback(0);
const handleClick = () =>
setState(
prev => prev + 1,
// important: use `s`, not the stale/old closure value `state`
s => console.log("I am called after setState, state:", s)
);
return (
<div>
<p>Hello Comp. State: {state} </p>
<button onClick={handleClick}>Click me</button>
</div>
);
}
function useStateCallback(initialState) {
const [state, setState] = useState(initialState);
const cbRef = useRef(null);
const setStateCallback = useCallback((state, cb) => {
cbRef.current = cb;
setState(state);
}, []);
useEffect(() => {
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null;
}
}, [state]);
return [state, setStateCallback];
}
ReactDOM.render(<App />, document.getElementById("root"));
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>
_x000D_
Source: Stackoverflow.com