@wangyouhua Great question: What is the difference between these versions?
functionuseTimeout1(callback,timeout){constcallbackRef=React.useRef(null)callbackRef.current=callbackReact.useEffect(()=>{if(!timeout&&timeout!==0)returnconstid=setTimeout(()=>callbackRef.current(),timeout)return()=>clearTimeout(id)},[timeout])}// vsfunctionuseTimeout2(callback,timeout){React.useEffect(()=>{if(!timeout&&timeout!==0)returnconstid=setTimeout(()=>{callback()},timeout)return()=>clearTimeout(id)},[timeout]// you will get a lint warning here about `callback`)}// vsfunctionuseTimeout3(callback,timeout){React.useEffect(()=>{if(!timeout&&timeout!==0)returnconstid=setTimeout(()=>{callback()},timeout)return()=>clearTimeout(id)},[timeout,callback])}
You can see the difference in the usage, in this example I want to log the value of someState after 5 seconds:
const[someState,setSomeState]=React.useState(0)useTimeout1(()=>{console.log(someState)},5000)// will correctly log the value of `someState` after 5 seconds// vsconst[someState,setSomeState]=React.useState(0)constcallback=React.useCallback(()=>{console.log(someState)},someState)useTimeout2(callback,5000)// this will always log 0, even if `someState` changes // vsconst[someState,setSomeState]=React.useState(0)constcallback=React.useCallback(()=>{console.log(someState)},someState)useTimeout3(callback,5000)// when `someState` changes, you need to wait another 5 seconds to see the results
As you can see version 2 and 3 have a bit different behavior. Version 2 is broken (and caught by the linting rules). Version 3 has unexpected behavior, it resets the timeout when someState is changed.
This has to with the 'capturing' of the callback. Once you pass a function to setTimeout that function can not be changed. Primitive values it references are captured. In order to get the expected result and don't put the burden of that correct handling on the user of the hook, we keep a reference to the 'current' callback.
So if you are using callbacks in hooks this is a great pattern:
constcallbackRef=React.useRef(null)callbackRef.current=callbackuseSomeAsynchronousHook(()=>{someAsynchrounousMethod(()=>{callBackRef.current()})},[]// there is no longer a dependency on the callback)
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
Nice comment! But I have a puzzle: why borther to use
I think we can eliminate the use of
useRef
, just use thecallback
argument in the placecallbackRef.current
.@wangyouhua Great question: What is the difference between these versions?
You can see the difference in the usage, in this example I want to log the value of
someState
after 5 seconds:As you can see version 2 and 3 have a bit different behavior. Version 2 is broken (and caught by the linting rules). Version 3 has unexpected behavior, it resets the timeout when
someState
is changed.This has to with the 'capturing' of the callback. Once you pass a function to
setTimeout
that function can not be changed. Primitive values it references are captured. In order to get the expected result and don't put the burden of that correct handling on the user of the hook, we keep a reference to the 'current' callback.So if you are using callbacks in hooks this is a great pattern: