What is wrong with the useCallback below that I do not get the values below every time the function onRefresh
is called ?
How can I make it return the expected values using Hooks
?
Example when I call onRefresh 2x
values expected:
true
0
20
false
true
0
20
false
values: received
false
0
0
false
false
20
20
false
initialization of the state variables
const [refreshing, setRefreshing] = useState(false)
const [offsetQuestions, setOffsetQuestions] = useState(0)
Function call with useCallback:
const fetchSomeData = async () => {
await new Promise(resolve => setTimeout(resolve, 3000)) // 3 sec
}
const onRefresh = useCallback( async () => {
setRefreshing(true)
setOffsetQuestions(0)
console.log(refreshing)
console.log(offsetQuestions)
await fetchSomeData()
setOffsetQuestions(20)
setRefreshing(false)
console.log(offsetQuestions)
console.log(refreshing)
}, [refreshing, offsetQuestions])
Where the function is called:
<FlatList
data={questionsLocal}
refreshing={refreshing}
onRefresh={onRefresh}
...
/>
What you are getting is the expected behaviour in hooks. It's all about closures. Each render in react has its own props, state, functions and event handlers and they forever stay the same for that particular render. So whats happening here is that the useCallback
is closing over the state variables for that particular render, So the console.log
even after a setState
will always give you the value of the state for that particular render because of closure.
This situation confuses many developers who start using hooks. You would expect that React will be reactive and will change the value of the state after you called the setX
, but if you think about it, you can not expect React to stop the execution flow change the state variable and continue execution.
So, what you are getting is the state variable from the scope of the creation of the useCallback
(Read it again and again).
Let's break it down to two parts, the scope, and useCallback, as these are two different things you must understand to be able to use useState
and useCallback
.
Scope:
Lets say that you have a state called age
and a function that write/reads this state (For now, without using useCallback
)
const [age, setAge] = useState(42);
const increaseAge = () => {
setAge(age + 1); // this "age" is the same "age" from above
console.log(age); // also this one
}
From this you can see that every render a new age
variable is defined (The value if given by React), also a new increaseAge
function is defined with access to the scope that holds the age
variable.
This is how the scope works, this is why you don't get a new value of age
just after calling setAge
. To be able to get a new value on age
React will have to re-render the component, so a new age
variable will be defined on a new scope. And this is exactly what happens after calling setAge
, React will trigger a new render.
useCallback
Now, let's say that you don't want to define the increaseAge
function again and again, you want to keep the same reference (Can help preventing useless renders). This is where you'd want to use useCallback
.
When using useCallback
, React provides the same function reference each render, unless one of the dependencies changes.
Without giving the useCallback
the dependencies, React will not recreate the function and the function will access to the scope that it was created on (First render), when giving the useCallback
the correct dependencies, it will be recreated, and now have access to the scope with the latest state variable.
This should explain why you must provide dependencies, and how state variables are accessed (scope).
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