Hooks useCallback keeps using old values when refreshing list - javascript

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).

You can use UseRef hook to use the current value of state instead of lexical value.
const ref = useRef();
useEffect(() => {
ref.current = value;
});
This how you can set value in a reference and use it wherever you want.
Below is the link where you can understand this problem in detail and with possible solutions
https://dmitripavlutin.com/react-hooks-stale-closures/
Thanks

Related

Why am I getting 'undefined' when trying to use the useState hook in React?

I am trying to use the useState hook in React to set an initial value as an empty function, but when I try to console.log the state variable, I get undefined.
Here is the code I am using:
import {useState} from 'react';
function MyComponent() {
const [callback, setCallback] = useState(() => {});
console.log(callback);
return <div>My component</div>;
}
I have also tried using the useEffect hook to update the state, but I am still getting undefined.
I am new to React and I am not sure what I am doing wrong. Can someone please help me understand why I am getting undefined and how I can fix it?
Passing a function to useState indicates lazy initialization - the function gets invoked once when the component mounts, and the value returned from the function determines the initial value.
If you want state to be a function, you'll need to have a function that returns a function.
const [callback, setCallback] = useState(() => () => {});
But it rarely makes sense for state to be a function. Use something more appropriate like useCallback, or just declare a plain function.
const callback = () => {
// ...
};
useState can optionally use a function to produce the initial value as a performance enhancement. So it's seeing your function that returns undefined as an initializer.
Try
// ,----- this is a function that returns a
// v function that does nothing
useState(() => () => {})
The argument you pass to useState is the initial value that React holds on to for every render until that state needs to change (through the "set function" which is the second value returned from the useState hook.)
We don't typically use functions as that initial value - but it can be done as explained in the other answers!
Since we're using functions (the useState hook) inside of functions (your component) we're creating closures that are going to be re-created every render. Now there's a serious issue with recreating things every render: the component can't "remember" anything when the closure gets recreated! This is why we have state. It allows the component to remember what previously exists.
We use the useEffect hook to "step outside" of React and go sync to an external system. Often we useEffect to retrieve some data:
import React, {useState, useEffect} from 'react';
function MyComponent() {
const [data, setData] = useState();
useEffect(() => {
async function retrieveData(){
const myData = await fetch(https://whatever...)
// now pass the data to the set state function
setData(myData)
}
}, [])
return <div>My component</div>;
}
I highly advise you to read through the new React docs here They do an excellent job of walking you through everything!
If you want to use useState's initial value as a function, you need to use Lazy Initial State:
const [callback, setCallback] = useState(functionName())
So if you do:
function functionName(){
return 'some computations'
}
Now if you log, functionName will return some computations as a result(You can return anything you want).

Why does React Linter complain about a const variable not being added to the dependency array?

If we have a function that fetches user data as such:
const fetchUserData = () => {
/* code here */
}
And then we have a useEffect with an empty array so it runs once after the component has rendered:
useEffect(() => {
fetchUserData();
}, []);
Then eslint/tslint throws this warning:
React Hook useEffect has a missing dependency: 'fetchUserData'. Either include it or remove the dependency array.
I get that this is good practice if say our function variable was a let or var but how would the fetchUserData variable change if it's declared with a const keyword.
Is this an error on the linter and we should just ignore it or is there something that I'm missing?
Whether the "variable"¹ referring the function is let, var, or const is irrelevant. Remember that every time your component needs to be rendered, your component function gets called again, which creates an entirely new execution context with its own const (or let or var) "variable" with a new fetchUserData function assigned to it. useEffect will only ever call the first one of those that's created (because you have a [] dependency array). For this specific situation it may be harmless, but in the general case you run the risk of having closures over stale data, hence the linter warning.
If you don't use any state or props in fetchUserData, you can relocate it inside the useEffect callback, which both gets rid of the error and avoids recreating a function on every render that you're not going to use. (If you do use state or props in fetchUserData, it probably shouldn't only be called once when the component is mounted, but each time the state/props it uses changes.)
¹ More generally, binding (a binding of a name to a storage slot containing a value in the lexical environment object for the execution context of the function call).

React useRef or module scope to store UI-independent state

Let's say there is a UI-independent state called currentSelected in React functional component. It stores the currently selected item and will be used at some time.
There are two ways to store the state, useRef hook or module scope out of component.
useRef hook:
function Example() {
const currentSelected = useRef()
useEffect(() => {
// access currentSelected state
})
function handleClick(item) {
currentSelected.current = item
}
return (
<ul>
{items.map(item => <li onClick={() => handleClick(item)}>item.name</li>)}
</ul>
)
}
module scope:
let currentSelected = null
function Example() {
useEffect(() => {
// access currentSelected state
})
function handleClick(item) {
currentSelected = item
}
return (
<ul>
{items.map(item => <li onClick={() => handleClick(item)}>item.name</li>)}
</ul>
)
}
Which method is more suitable for storing UI-independent state like currentSelected?
And what is the application scenario of useRef and module scope in storing state?
========= Update ===========
UI-independence means that you don't want to trigger re-render after updating the state. In contrast, UI related states do this.
The difference between useRef and a module-scope variable
Just for completeness sake, I'll throw in useState as well.
useState: immutable data that's tied to the component instance and triggers render on change through the setter function.
useRef: mutable data that's also tied to the component instance, but doesn't trigger any renders on change.
A module scope variable: mutable data that's tied to the module, which also doesn't trigger any renders on change since it's completely outside of React.
Use-case for useRef
If you're mounting a component more than once, e.g. using it on two pages, useRef will ensure that each component instance has its own mutable value.
// Here, the Example component could be used in multiple places
// and each instance would successfully keep its own state while
// not triggering renders on changes.
function Example() {
const currentSelected = useRef()
useEffect(() => { /* access currentSelected state */ })
return (
<ul>
{items.map(item => (
<li onClick={() => { currentSelected.current = item }}>{item.name}</li>
))}
</ul>
)
}
Use-case for a module scope variable
If you're looking for a singleton-like pattern or a static-like variable, e.g. for some kind of app-wide shared data, then a module scope variable will make this possible, like in any vanilla JS module.
// Here, the count value will be initialized once and then shared between every
// instances across the app, ever.
let count = 0;
function Example() {
// Won't trigger a render, so some Example instance could still show the old value.
count += 1;
return <span>Combined renders of all the Example components: {count}</span>;
}
Note that it won't trigger a render when count changes, so you shouldn't use it like that.
The caveats
If used only once in a place where the component is also only mounted once, both patterns will appear to behave similarly, but in the end, it's a matter of time until something triggers a remount and then you'll be facing a weird bug.
You could also encounter problems when unit testing a module with a module scope variable in it since it may not be properly reset in-between test cases. A quick workaround is to just export the variable and let the test cases change its value, but be careful not to change it anywhere else. Though this should be evaluated as a case-by-case basis.
First one
For myself choose the first one.Because its works as individual inside function.You could use multiple example component as same class/function .
function check(a){
let one = 'overwrite'+a;
console.log(one)
}
check(1);
check(2);//as individual inside only
Second one
Its overwrite currentSelected variable on each example component execute
let one = null
//second one
function check() {
one = 'overwrite';
console.log(one)
}
console.log('Before =' + one)
check();
console.log('After =' + one);
why not use the useState hook. it's perfect for this secnario.
const [currentItem, setCurrentItem] = useState();
...
{items.map(item => <li onClick={() => setCurrentItem(item)}>item.name</li>)}
another problem with your example is that when you change the ref current property in the handleClick it doesnt triggered render, so the useEffect in your function will not run so you cant access the ref.
Your "currentItem" may be UI-independent, but it would be component-dependent. right? If currentItem is related to this component, you should use the React.useState hook. This is just what the state is for.
Global variables(not constants) should be avoided in OOP programming. For example, there would be only one global variable in well-written OOP code, i.e. theApp.

Is useState synchronous? [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 4 years ago.
In the past, we've been explicitly warned that calling setState({myProperty}) is asynchronous, and the value of this.state.myProperty is not valid until the callback, or until the next render() method.
With useState, how do I get the value of the state after explicitly updating it?
How does this work with hooks? As far as I can tell, the setter function of useState doesn't take a callback, e.g.
const [value, setValue] = useState(0);
setValue(42, () => console.log('hi callback');
doesn't result in the callback being run.
My other workaround in the old world is to hang an instance variable (e.g. this.otherProperty = 42) on the class, but that doesn't work here, as there is no function instance to reuse (no this in strict mode).
You can use useEffect to access the latest state after updating it. If you have multiple state hooks and would like to track the update on only part of them, you can pass in the state in an array as the second argument of the useEffect function:
useEffect(() => { console.log(state1, state2)}, [state1])
The above useEffect will only be called when state1 is updated and you shouldn't trust the state2 value from inside this function as it may not be the latest.
If you are curious about whether the update function created by useState is synchronous, i.e. if React batches the state update when using hooks, this answer provide some insight into it.
Well, if you refer to the relevant docs you'll find...
const [state, setState] = useState(initialState);
Returns a stateful value, and a function to update it.
During the initial render, the returned state (state) is the same as the value passed as the first argument (initialState).
The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component.
setState(newState);
During subsequent re-renders, the first value returned by useState will always be the most recent state after applying updates.
So your new state, whatever it is, is what you've just set in useState, i.e. the value of initialState. It goes directly to state, which updates reactively thereafter. Quoting further from the relevant docs:
What does calling useState do? It declares a “state variable”. Our variable is called count but we could call it anything else, like banana. This is a way to “preserve” some values between the function calls — useState is a new way to use the exact same capabilities that this.state provides in a class. Normally, variables “disappear” when the function exits but state variables are preserved by React.
If you'd like to do something whenever your state updates, then just use componentDidUpdate.(docs)

What is the intention of using React's useCallback hook in place of useEffect?

I'm trying to understand what the use case is for using React's useCallback hook in place of the useEffect hook.
They both appear to act as a listener for state changes of their inputs (examples taken from the React Docs):
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
But, the useEffect hook gives the additional benefit of cleaning up resources where you would have previously with componentWillUnmount.
So, what is a good use case for using useCallback? And, what am I missing here?
useEffect has very specific timing aspects related to it that you can read about here. The function specified will be executed after rendering is complete and the DOM has been updated. This will happen after each rendering where any of the values specified in the second-argument array change.
useCallback doesn't automatically execute anything. It returns a function that can be executed by whatever code needs to trigger it. There is no listening to changes that causes an execution of the callback. The array values just control what instance of the function is returned. The array values do not control the timing of the function execution.
A key use case is to pass this function as a prop to a child component to use as an event handler. useCallback allows you to define an inline function to use as an event handler (thus it has access to any other variables in the context where the function is defined) without the downside of passing a unique prop to the child every render. So long as the values in the second-argument array have not changed, the same function will be returned as was returned the previous rendering. So if the child component is a pure component, it will not be forced to re-render simply because of always receiving a unique event handler function.
without useCallback
const Parent = ()=> {
const [a, setA] = useState(null);
const eventHandler = ()=> {
// every render creates a unique instance of eventHandler
// even though it always does the same thing so long as 'a' hasn't changed
doSomethingWithA(a);
}
return <Child onClick={eventHandler}/>
}
with useCallback
const Parent = ()=> {
const [a, setA] = useState(null);
const eventHandler = useCallback(()=> {
// A unique function instance is passed in to useCallback on every render, but
// eventHandler will be set to the first instance of this function
// (i.e. potentially an instance of the function that was passed to useCallback
// on a previous rendering) that was passed to useCallback
// for the current value of 'a'.
doSomethingWithA(a);
}, [a]);
return <Child onClick={eventHandler}/>
}
This article provides a bit more detail than the React docs on the use case for useCallback and other hooks.
Related answer: Trouble with simple example of React Hooks useCallback

Categories

Resources