Is useState synchronous? [duplicate] - javascript

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)

Related

React hooks to update state based on the previous state value [duplicate]

This question already has answers here:
Why can't I directly modify a component's state, really?
(7 answers)
Closed 2 years ago.
Using hooks to update state based on the previous state value, I don't understand why modifying the existing object and passing that to setState() is bad. I know this won't cause a re-render since the state still points to the same reference, but beyond that, what is the issue? I don't understand how cloning the array, modifying that, then passing it to setState() fixes some unknown issue.
const [bigArr, setBigArr] = setState(Array(SOME_BIG_NUMBER).fill(false));
// (1) This seems to work, but is bad for some reason. But why?
bigArr[325] = true;
setBigArr(bigArr);
// (2) This is preferable for some reason. Why?
bigArrCopy = bigArr.slice();
bigArrCopy[325] = true;
setBigArr(bigArrCopy);
// (3) Is this OK? Why/Why not?
setBigArr(bigArrCopy => {
bigArrCopy[325] = true;
return bigArrCopy;
});
I know this won't cause a re-render since the state still points to the same reference, but beyond that, what is the issue?
Is that not enough? The reason to set state is because you want the component to rerender. If you're trying to get it to rerender and it doesn't, that's a pretty serious bug.
The underlying reason why react went with a model of immutable state is that it makes it very simple to tell whether state changed. Do a quick === between two states, and you immediately know whether it has changed. If you mutate your state, this feature is lost, and any code that depends on it breaks.
The first case will not work as expected, it will not re-render the component because React use shallow comparison which means that it will compare location of object and array, if the location not change React will not trigger re-render
// (1) This will not re-render the component, cuz the location of bigArr are not changed
bigArr[325] = true;
setBigArr(bigArr);
Happen the same with third case
You may say you can fix it by call setBigArr([...bigArr]);. But there is still problems
This is an simple situation you get unexpected result
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [arr, setArr] = useState([1]);
const handleClick = () => {
createTimeout()
// Case1: directly set state
arr[0]= 2
setArr(arr);
// Case1: Not directly set state
// const newArr = [...arr]
// newArr[0]= 2
// setArr(newArr);
};
const createTimeout = () =>{
setTimeout(() =>{
// You expect number 1 here because you call this before update state
console.log(arr[0])
},2000)
}
return (
<div className="App">
<h1>{arr[0]}</h1>
<div onClick={handleClick}>change</div>
</div>
);
}
We call createTimeout before setState so we will expect number 1 will be logged but:
Case 1: you will get number 2 because you mutated original array
Case 2: you will get number 1 (expected)
Check out the codesandbox
React checks to see if the bigArrState !== prevBigArrState before re-rendering. It does not check the contents of the array. It checks if it's the same instance. In your first example that will result in false, and the component will not re-render. When you use bigArr.slice(), you are creating an entire new array therefore bigArrState !== prevBigArrState results in true, allowing the component to re-render.
Your last example will causes issues because the updater func does not get passed bigArrCopy but rather bigArrState (same instance).
https://reactjs.org/docs/react-component.html#setstate
Rather than creating and storing an entire clone in memory you can do the following:
setBigArr([
...bigArr.slice(0, 325),
true,
...bigArr.slice(326),
]);

Hooks useCallback keeps using old values when refreshing list

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

Why doesn't useState function initialize state every time?

import React, { useState } from "react";
function HookCounter() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count {count}</button>
</div>
);
}
export default HookCounter;
React calls this function every time when it needs to re-render.
But why doesn't it initialize the state every time?
When exit the function, life of variables is ended, isn't it?
But how does it keep saving values of states?
I don't understand.
In useState function, is there any logic for that?
useState as a function is storing the value you gave it within React's core. When the component re-renders, React is going to pass the updated count from its core to the component.
More information here.
State is initialized only once when you create the component, this is how React works. From the documentation:
What does calling useState do? It declares a “state variable”. Normally, variables “disappear” when the function exits but state variables are preserved by React.
Just to have the context here, let me summarize what is useState and how it works in more details.
What is useState:
So useState is a hook which helps you to handle state in a functional component.
How is it working?
Once you call useState you need to pass the initial value of the state what you would like to use in your functional component. It returns a pair of values the count and setCount.
So let's have your example below:
const [count, setCount] = useState(0);
So useState returned two items where count is the current value. And the second item, setCount is a function which can be used to update the value of the count state.
count can be used to represent the state for example the value in a div element:
return (
<div>{count}</div>
)
In order to update the value you can achieve by calling setState(12).
From the docs you can read further here.
According to the React official website:
React keeps track of the currently rendering component. Thanks to the Rules of Hooks, we know that Hooks are only called from React components (or custom Hooks — which are also only called from React components).
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.
Reference:
How does React associate Hook calls with components?
Why doesn't useState function initialize State every time?
I think you're confused with:
const [count, setCount] = useState(0);
As it's just a variable!?!
Yes, it's just a variable but it will take everytime the function runs from the useState hook. And it is the local state like you have seen in class based component.

How does useState() in react retrieve the correct state object and function for a functional component when using the state hook? [duplicate]

This question already has an answer here:
How does React implement hooks so that they rely on call order
(1 answer)
Closed 3 years ago.
I am currently working on my understanding of the useState hook from react.
What I would like to know is; when useState is called how is it able to correctly retrieve the state object and the function that can be used to modify it, for that specific functional component (assuming it has already been created the first time it was called.)
Another way to phrase my question would be where does count and setCount exist? useState() will obviously return a different state object and modifier function depending on which functional component useState is called in, so how does that work?
My guess would be that a closure is formed which means that this functional component has a lexical environment which consists of any local variables that were in-scope at the time the closure was created and this is what makes count and setCount available when useState is called. But I haven't been able to confirm this and since react is involved I could be way off.
export const MyFunctionalComponent = () => {
const [count, setCount] = useState(0);
return (
<h1>This is my component.</h1>
);
}
Can anyone clear this up for me?
#edit: I'm pretty sure I'm way off with my thinking about closures, looking at the react library source code I found the implementation for the useState function.
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
I'm probably going to have to dig in and unpack this to get an answer.
I found the following on the React documentation page which at least gives a basic description on how useState is able to get back local state for functional components.
React keeps track of the currently rendering component. Thanks to the Rules of Hooks, we know that Hooks are only called from React
components (or custom Hooks — which are also only called from React
components).
There is an internal list of “memory cells” associated with each
component. They’re just JavaScript objects where we can put some data.
When you call a Hook like useState(), it reads the current cell (or
initializes it during the first render), and then moves the pointer to
the next one. This is how multiple useState() calls each get
independent local state.

is the useState's setter guaranteed to be the same?

Say I want to create the often wanted useInputState hook:
function useInputState(initialValue) {
const [value, setValue] = useState(initialValue);
const onChange = useCallback(
e => setValue(e.target.value),
[ /* ??? */ ]
);
return { value, onChange };
}
Would I need to add the setValue setter function to the callback's dependencies?
Can we count on the setter to always stay the same?
This seems to work when I try it, but is this good practice? Or should we assume ANYTHING in the callback's closure can change and affect its implementation?
(I can think of more examples that eventually raise the same question)
Yes, the setter from useState and similarly the dispatch from useReducer do not change.
This portion of the docs covers the pattern of passing the dispatch method to descendant components via context and contains the following:
If you use context to pass down the state too, use two different
context types — the dispatch context never changes, so components that
read it don’t need to rerender unless they also need the application
state.
Yes, useState state setter is the same function; even if it wouldn't, it would be expected to update the state. It is supposed to be used like the question shows.
The common usage is explained better in useReducer documentation:
useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
And useState uses useReducer internally.
The concerns with a scope where useState was called are applicable only to useState state value which stays the same in the scope of a callback where it's used, e.g. this problem. In case current state should be used, it should be retrieved with state updater function, like setValue(currentState => currentState + 1).

Categories

Resources