最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React useCallback with debounce works with old value, how to get actual state value? - Stack Overflow

programmeradmin1浏览0评论

I can not fulfill all the conditions:

  1. I need some function inside useCallback, because I set it as props to child ponent (for re-render preventing)
  2. I need to use debounce, because my function is "end point" and can be called ~100times/sec
  3. 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:

  1. I need some function inside useCallback, because I set it as props to child ponent (for re-render preventing)
  2. I need to use debounce, because my function is "end point" and can be called ~100times/sec
  3. 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 called save() 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 is save and instead of alterting 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
Add a ment  | 

1 Answer 1

Reset to default 8

First 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

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论