I can not fulfill all the conditions:
- I need some function inside
useCallback
, because I set it as props to child ponent (for re-render preventing) - I need to use
debounce
, because my function is "end point" and can be called ~100times/sec - I need to get current (actual values) after debounce.
I have problem with last point, my values after debounce (1000ms) is outdated.
How to get current values using useCallback
+ debounce
? (values in alert must to be same as page)
//ES6 const, let
//ES6 Destructuring
const { Component, useCallback, useState, useEffect } = React;
const SUBChildComponent = (props) => (<button onClick={props.getVal}>GetValue with debounce</button>);
const ChildComponent = () => {
// some unstable states
const [someVal1, setSomeVal1] = useState(0);
const [someVal2, setSomeVal2] = useState(0);
const [someVal3, setSomeVal3] = useState(0);
// some callback witch works with states AND called from subClild ponents
const getVal = useCallback(_.debounce(() => {
alert(`${someVal1}\n${someVal2}\n${someVal3}`);
}, 1000), [someVal1, someVal2, someVal3]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal1(someVal1 + 1), 50);
return () => clearInterval(id);
}, [someVal1]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal2(someVal2 + 1), 100);
return () => clearInterval(id);
}, [someVal2]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal3(someVal3 + 1), 250);
return () => clearInterval(id);
}, [someVal3]);
return <React.Fragment><SUBChildComponent getVal={getVal}/><br/>{someVal1}<br/>{someVal2}<br/>{someVal3}
</React.Fragment>;
};
class App extends Component {
render() {
return (<div><ChildComponent/></div>);
}
}
ReactDOM.render(<App/>, document.querySelector(".container"));
<script src=".8.6/umd/react.production.min.js"></script>
<script src=".8.6/umd/react-dom.production.min.js"></script>
<script src=".js/3.5.0/lodash.min.js"></script>
<div class="container"></div>
I can not fulfill all the conditions:
- I need some function inside
useCallback
, because I set it as props to child ponent (for re-render preventing) - I need to use
debounce
, because my function is "end point" and can be called ~100times/sec - I need to get current (actual values) after debounce.
I have problem with last point, my values after debounce (1000ms) is outdated.
How to get current values using useCallback
+ debounce
? (values in alert must to be same as page)
//ES6 const, let
//ES6 Destructuring
const { Component, useCallback, useState, useEffect } = React;
const SUBChildComponent = (props) => (<button onClick={props.getVal}>GetValue with debounce</button>);
const ChildComponent = () => {
// some unstable states
const [someVal1, setSomeVal1] = useState(0);
const [someVal2, setSomeVal2] = useState(0);
const [someVal3, setSomeVal3] = useState(0);
// some callback witch works with states AND called from subClild ponents
const getVal = useCallback(_.debounce(() => {
alert(`${someVal1}\n${someVal2}\n${someVal3}`);
}, 1000), [someVal1, someVal2, someVal3]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal1(someVal1 + 1), 50);
return () => clearInterval(id);
}, [someVal1]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal2(someVal2 + 1), 100);
return () => clearInterval(id);
}, [someVal2]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal3(someVal3 + 1), 250);
return () => clearInterval(id);
}, [someVal3]);
return <React.Fragment><SUBChildComponent getVal={getVal}/><br/>{someVal1}<br/>{someVal2}<br/>{someVal3}
</React.Fragment>;
};
class App extends Component {
render() {
return (<div><ChildComponent/></div>);
}
}
ReactDOM.render(<App/>, document.querySelector(".container"));
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<div class="container"></div>
Share
Improve this question
asked May 19, 2020 at 8:49
mixalbl4mixalbl4
4,0251 gold badge35 silver badges47 bronze badges
5
- Does this answer your question? React - useState - why setTimeout function does not have latest state value? – JDansercoer Commented May 19, 2020 at 8:58
- I don't understand why you debouce the getter. Wouldn't it make much more sense to debounce setting the state and simply pass the current state down as props? – trixn Commented May 19, 2020 at 9:07
-
@trixn, nope. This is a library and I just give
save()
method for users code. Users code doesnt know about any library private states and theirs values. They just made ponent with<AnyComp save={save}>
and calledsave()
a lot of times. @JDansercoer nope, this is a similar problem but not the same, and solutions is totally different. – mixalbl4 Commented May 19, 2020 at 9:18 -
@MixerOID So in your real application
getVal
issave
and instead ofaltert
ing you want to save the current values from your state to your backend (which triggers an api call)? – trixn Commented May 19, 2020 at 9:24 - @trixn, yep. This code is just "simplified example" for showing the problem in a simplest way. – mixalbl4 Commented May 19, 2020 at 9:27
1 Answer
Reset to default 8First of all you must note that the debounce function sets the states from its closure when its created. Now the function is executed a few seconds later and by that time the states would have changed. Also a new instance of debounce will be created each time states are updated, so if at all you use debounce function onClick, it won't work correctly as different calls will be calling different instances of debounce function and not the same one
The solution in such cases is to pass on the state values as argument to debounce function instead of letting it rely on the closure. It however it still use the value with which debounce was called, as you can see in below snippet
//ES6 const, let
//ES6 Destructuring
const { Component, useCallback, useState, useEffect } = React;
const SUBChildComponent = ({getVal, someVal1,someVal2,someVal3}) => (<button onClick={() => getVal(someVal1,someVal2,someVal3)}>GetValue with debounce</button>);
const ChildComponent = () => {
// some unstable states
const [someVal1, setSomeVal1] = useState(0);
const [someVal2, setSomeVal2] = useState(0);
const [someVal3, setSomeVal3] = useState(0);
// some callback witch works with states AND called from subClild ponents
const getVal = useCallback(_.debounce((val1, val2, val3) => {
alert(`${val1}\n${val2}\n${val3}`);
}, 1000), []); // create debounce function only once
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal1(someVal1 + 1), 50);
return () => clearInterval(id);
}, [someVal1]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal2(someVal2 + 1), 100);
return () => clearInterval(id);
}, [someVal2]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal3(someVal3 + 1), 250);
return () => clearInterval(id);
}, [someVal3]);
return <React.Fragment><SUBChildComponent someVal1={someVal1} someVal2={someVal2} someVal3={someVal3} getVal={getVal}/><br/>{someVal1}<br/>{someVal2}<br/>{someVal3}
</React.Fragment>;
};
class App extends Component {
render() {
return (<div><ChildComponent/></div>);
}
}
ReactDOM.render(<App/>, document.querySelector(".container"));
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<div class="container"></div>
Now another solution is to keeps refs of state and use them within debounce function which is what you want in your case
//ES6 const, let
//ES6 Destructuring
const { Component, useCallback, useState, useEffect, useRef } = React;
const SUBChildComponent = React.memo(({getVal}) => {
console.log('child render');
return <button onClick={() => getVal()}>GetValue with debounce</button>;
});
const ChildComponent = () => {
// some unstable states
const [someVal1, setSomeVal1] = useState(0);
const [someVal2, setSomeVal2] = useState(0);
const [someVal3, setSomeVal3] = useState(0);
const someVal1Ref = useRef(someVal1);
const someVal2Ref = useRef(someVal2);
const someVal3Ref = useRef(someVal3);
useEffect(() => {
someVal1Ref.current = someVal1;
someVal2Ref.current = someVal2;
someVal3Ref.current = someVal3;
}, [someVal1, someVal2, someVal3])
// some callback witch works with states AND called from subClild ponents
const getVal = useCallback(_.debounce(() => {
alert(`${someVal1Ref.current}\n${someVal2Ref.current}\n${someVal3Ref.current}`);
}, 1000), []); // create debounce function only once
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal1(someVal1 + 1), 50);
return () => clearInterval(id);
}, [someVal1]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal2(someVal2 + 1), 100);
return () => clearInterval(id);
}, [someVal2]);
// some synthetic changes
useEffect(() => {
const id = setInterval(() => setSomeVal3(someVal3 + 1), 250);
return () => clearInterval(id);
}, [someVal3]);
return <React.Fragment><SUBChildComponent getVal={getVal}/><br/>{someVal1}<br/>{someVal2}<br/>{someVal3}
</React.Fragment>;
};
class App extends Component {
render() {
return (<div><ChildComponent/></div>);
}
}
ReactDOM.render(<App/>, document.querySelector(".container"));
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<div class="container"></div>
PS. Such kind of implementation are way easier in class ponents and don't need any work around as you are not dependent on closures