useState hook: update a state based on another just updated state - reactjs

I'm currently looking in to React's funtion Component and the useState hook.
const [stateA, setStateA] = useState(somevalue)
const [stateB, setStateB] = useState(somevalue)
function onClick(value){
setStateA(stateA => transformedValue) //a is now transformedValue
//Use transformedValue to update state B
setStateB(...)
}
My question is how to use the newly updated stateA(transformedValue) to updateB after that.
So far i could only access the previous value of State A, not the transformedValue.
I know i could calculate transformedValue outside of the setStateA function, then use that to setStateB. But is it the correct way?
I know can also merge stateA and stateB into one State too.
What is the correct approach? Is there another more preferred way?

The best approach is using useEffect as mentioned.
useEffect(() => {
setCounter2(count + 1);
}, [count]);
Because of you obsessed with rendering once, try this:
function App() {
const [count, setCount] = useState(0);
const [dummy, setDummy] = useState(0);
useEffect(() => {
console.log(`Renderderd ${count} times`);
});
function handleClick() {
setCount(prevCounter => {
setDummy(prevCounter + 1);
return prevCounter + 1;
});
}
return (
<>
<div>You clicked count {count} times!</div>
<div>Dummy= {dummy} !</div>
<button onClick={handleClick}>Click me for rendering!</button>
</>
);
}

As the method setState is asynchronous.
Your stateA won't update immediately when setState is called.
A way to implement this idea is that
to "watch" a stateA by using useEffect method to ensure stateA is updated.
code seems like this:
useEffect(()=>{
setStateB(stateA); // or do anything with updated stateA
},[stateA]);

Related

understanding useCallback and setState

I have some simple lines of code to test the useCallback and setState hook. First I try to write like this:
const App = () => {
const [count, setCount] = useState(0)
const increase = useCallback(() => {
setCount(count + 1) // Passing value to change state
}, [])
console.log(count)
return (
<div id="main">
<h1>Count: {count}</h1>
<button onClick={increase}>Increase</button>
</div>
)
}
export default App;
and then, passing the callback to setCount instead like this:
const App = () => {
const [count, setCount] = useState(0)
const increase = useCallback(() => {
setCount(count => count + 1) // Passing callback
}, [])
console.log(count)
return (
<div id="main">
<h1>Count: {count}</h1>
<button onClick={increase}>Increase</button>
</div>
)
}
export default App;
I know it is weird to use useCallback in this situation, just for some testing. The question is that why I pass the callback to setCount, the Increase button work correctly, whereas the first one will just re-render on the first click
In the first case useCallback closes over the count and at that time count was 0 so It always changes value to 1
So to make it working you should add count dependency to useCallback as:
CODESANDBOX LINK
const increase = useCallback(() => {
setCount(count + 1); // Passing value to change state
}, [count]);
In the second case it is not taking the value from the closure instead it is taking the current value and adds one to it and set the value.
ADDITIONAL INFO
You can use react-hooks/exhaustive-deps eslint rule to know which dependency is missing and tell you how to correct it.
useCallback() hasn't been used correctly here: the dependencies array should have count in it. So:
const increase = useCallback(() => {
setCount(count + 1) // Passing value to change state
}, [count])
With an empty array as a dependency, it will be memoized for the lifetime of the component and will not update the current count value.
Source: https://reactjs.org/docs/hooks-reference.html#usecallback
Code sandbox
On a side note, using useCallback() is "overoptimizing" in this case, we are complicating the code while the performance impact to the end users is not noticeable. I would only use it to prevent rerenders in complex views e.g. in a huge list.

Why my State values cannot be updated with a timer in react?

Im new in react and Im doing some basic stuff... so, I started using the states and I found it quite useful, however, there is one thing that doesnt make sense in my mind.
It all started by having a time displayed, something like this
function MyAPp() {
const initValue = new Date().toLocaleTimeString();
const [value, setCount] = React.useState(initValue);
function updateValue() {
setCount(new Date().toLocaleTimeString())
}
setInterval(updateValue, 1000)
return <div>
<label> The value of my state is { value } </label>
</div>
So, the above works great, updates the time every second and nothing wrong with it...
Now when I do this:
function MyApp() {
const [count, setCount] = React.useState(123);
function updateValue() {
setCount(count + 1)
}
setInterval(updateValue, 1000)
return <div>
<label> The value of my state is { count } </label>
</div>
}
When it reaches 8 or 9, starts to have a weird behaviour, it starts to get updated less than every second, and the update goes jumping from number to number, like from 8 to 9 and then back to 8, then quickly back to 9 and 10, 11 and back to 9 or 8 again and it just gets crazy....
Checking about the limitations of getting the value and use it to set the count, makes no sense why is failing this way.
And even worse, why the interval seems to be affected, feels like it would be getting into a loop by calling MyApp() or something like that, which is not happening...
Any idea what could be wrong with this?
There are 2 issues in the above-mentioned implementation.
setInterval is being called on every render.
This can be solved by calling this inside useEffect without any dependencies. It'll make sure the interval is being called only once.
This is a case of stale props or state in react hook. For more refer the official documentation.
The setter method of state accepts a callback. It gives the current state in the callback which will always have the final update value.
Kent Dodds mentions this as a Lazy initialization of state. Here's a really good blog on the same.
Here's the working example solving this issue at codesandbox
Code
import { useState, useEffect } from "react";
export default function App() {
const [count, setCount] = useState(123);
useEffect(() => {
setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
}, []);
return (
<div>
<label> The value of my state is {count} </label>
</div>
);
}
becuase setInterval is a method that calls a function or runs some code after specific intervals of time, as specified through the second parameter.
clearInterval is a function or block of code that is bound to an interval executes until it is stopped. After the React component unmounts the interval is cleared.
by two methods , your scheduling works good in display count.
export default function App() {
const [count, setCount] = React.useState(123);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(interval);
}, [count]);
return (
<div>
<label> The value of my state is {count} </label>
</div>
);
}
You are doing it absolutely wrong, firstly as you can see your setInterval is registered on every render which is causing the unexpected behavior and secondly it's always recommended to merge the old and new state when using setInterval as the value will remain same in the setInterval callback.
Here is the solution to your problem.
function MyApp() {
const [count, setCount] = React.useState(123);
const updateValue = React.useCallback(() => {
setCount((old) => old + 1);
}, []);
React.useEffect(() => {
setInterval(updateValue, 1000);
}, [updateValue]);
return (
<div>
<label> The value of my state is {count} </label>
</div>
);
}
So in this solution we have used React.useEffect which has only one dependency which means when the dependency will be updated the call of React.useEffect will be invoked but in this case it will only be invoked once the component is rendered.
Checkout this demo

useState() undefined in React just after set

I have a problem and I do not undersatnd why the hook return undefined :
import React, { useEffect, useState } from 'react';
function App(){
const [globalVariable, setGlobalVariable] = useState();
useEffect(()=>{
const test = 5
console.log(test) // return 5
setGlobalVariable(test)
console.log(globalVariable) // return undefined
},[]);
return (
<div>
</div>
);
}
export default App;
How can I do in order to set directly a new value for the globalVariable ?
As mentioned by other members, setState runs asynchronously in a queue-based system. This means that if you want to perform a state update, this update will be piled up on the queue. However, it does not return a Promise, so you can't use a .then or a wait. Because of this, we need to be aware of possible problems that might occur while manipulating states in React.
Say we have:
// PROBLEM # 1
export function Button() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
console.log(count); // returns 0
}
return (
<button onClick={increment}>Increment<\button>
)
}
That is basically the same problem you have. Since setCount is asynchronous, the console.log will return the value before the updates (which is 0 here). In your case, it will return undefined because you didn't pass any initial state in the useState hook. Other thing you did different was to fire the state change from inside the useEffect hook, but that doesn't matter for the problem at hand.
To fix this, a good practice is to store the new state in another variable, as follows:
// SOLUTION TO PROBLEM # 1
export function Button() {
const [count, setCount] = useState(0);
function increment() {
const newCountValue = count + 1;
setCount(newCountValue);
console.log(newCountValue); // returns 1
}
return (
<button onClick={increment}>Increment<\button>
)
}
This basically answers your question.
But, there are still other scenarios where state updates might not behave as one would expect. It is important to be aware of these scenarios to avoid possible bugs. For example, let's assume that we are updating a given state multiple times in a row, like:
// PROBLEM # 2
export function Button() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
console.log(count); // returns 0, but the final state will be 1 (not 3)
}
return (
<button onClick={increment}>Increment<\button>
)
}
Here, console.log still returns 0 for the same reason described in Problem #1. But, if you are using the count state somewhere in your app, you will see that the final value after the onClick event is 1, not 3.
Here is how you can fix this:
// SOLUTION TO PROBLEM # 2
export function Button() {
const [count, setCount] = useState(0);
function increment() {
setCount((oldState) => oldState + 1);
setCount((oldState) => oldState + 1);
setCount((oldState) => oldState + 1);
console.log(count); // returns 0, but the final state will be 3
}
return (
<button onClick={increment}>Increment<\button>
)
}
By passing a function to setState, the new state is computed using the previous value on the queue and not the initial one.
As a final example, say we are calling a function right after trying to mutate our state, but this function depends on the state value. Since the operation is asynchronous, that function will use the "old" state value, just like in Problem #1:
// PROBLEM # 3
export function Button() {
const [count, setCount] = useState(0);
onCountChange() {
console.log(count); // returns 0;
}
function increment() {
setCount(count + 1);
onCountChange();
}
return (
<button onClick={increment}>Increment<\button>
)
}
Notice in this problem that we have a function onCountChange that depends on an external variable (count). By external I mean that this variable was not declared inside the scope of onCountChange. They are actually siblings (both lie inside the Button scope). Now, if you are familiar with the concept of "pure functions", "idempotent" and "side effects", you will realize that what we have here is a side-effect issue -- we want something to happen after a state change.
Roughly speaking, whenever you need to handle some side-effects in React, this is done via the useEffect hook. So, to fix our 3rd problem, we can simply do:
// SOLUTION TO PROBLEM # 3
export function Button() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count); // returns 0;
}, [count]);
function increment() {
setCount(count + 1);
}
return (
<button onClick={increment}>Increment<\button>
)
}
In the solution above, we moved the content of the onCountChange to useEffect and we added count as a dependency, so whenever it changes, the effect will be triggered.
Because state only has new value when component re-render. So just put console.log(globalVariable) outside useEffect like this:
useEffect(() => {
const test = 5;
console.log(test); // return 5
setGlobalVariable(test);
}, []);
console.log(globalVariable);
Two things:
useState has an initial value, defaulted to undefined.
const [globalVariable, setGlobalVariable] = useState(123); // initial value set to 123
setGlobalVariable is not a synchronous operation. Changing it will not mutate globalVariable immediately. The value in scope will remain the same until the next render phase.
This is because component is not re-rendering. You need to keep the value between the renders and for that you need to change the variable to a state using useState. Check below code for better understanding:
import React, { useEffect, useState } from "react";
function Test() {
const [globalVariable, setGlobalVariable] = useState();
const [test, setTest] = useState(5);
useEffect(() => {
// const test = 5;
// console.log(test); // return 5
setGlobalVariable(test);
console.log(globalVariable); // return undefined
console.log(test); // return 5
}, []);
return <div></div>;
}
export default Test;
This is because React state update is queue based system. It won't update immediately. But if you render a global variable value, you get the required one.
If you have same requirement in class-based component, then you need to pass callback function to setState. It will execute as soon as state is updated.
this.setState(stateupdate, callback)
Once the state is updated, the callback function will execute with the latest state.
const StackOverflow = () => {
const [globalVariable, setGlobalVariable] = useState();
useEffect(()=>{
const test = 5
console.log(test) // return 5
setGlobalVariable(test)
console.log(globalVariable)
},[]);
return (
<Container maxWidth="lg" style={{paddingTop:"5%"}}>
<div>
<h1>StackOverflow - {globalVariable}</h1>
</div>
</Container>
)
}
This is well-explained in the official documentation, §caveats:
The set function only updates the state variable for the next render. If you read the state variable after calling the set function, you will still get the old value that was on the screen before your call.
And especially in §troubleshooting: "I’ve updated the state, but logging gives me the old value"
that's because the setGlobalVariable(test); takes some time, and you are trying to do it in the useEffect, which runs only once, you should use a setTimeOut function to take some time to show the state, try this and let me know is it working or not.
const [globalVariable, setGlobalVariable] = useState();
useEffect(() => {
const test = 5;
// console.log(test); // return 5
setGlobalVariable(test);
setTimeout(() => {
console.log(globalVariable);
}, 2000);
}, []);

Logical understanding react hooks, difference between useState and useEffect (or state and lifecycle methods)

I cannot understand the difference between useState and useEffect. Specifically, the difference between state and lifecycle methods. For instance, I have watched tutorials and seen this example for useEffect:
const UseEffectBasics = () => {
const [value, setVal] = useState(0);
const add = () => {
setVal((x) => { return x + 1 })
}
useEffect(() => {
if (value > 0) {
document.title = `Title: ${value}`
}
},[value])
return <>
<h1>{value}</h1>
<button className="btn" onClick={add}>add</button>
</>;
};
When we click the button, the title of the document shows us increasing numbers by one. When I removed the useEffect method and did this instead:
const UseEffectBasics = () => {
const [value, setVal] = useState(0);
document.title = `Title: ${value}`
const add = () => {
setVal((x) => { return x + 1 })
}
return <>
<h1>{value}</h1>
<button className="btn" onClick={add}>add</button>
</>;
};
It worked same as the previous code.
So, how does useEffect actually work? What is the purpose of this method?
P.S. Do not send me links of documentation or basic tutorials, please. I have done my research. I know what I am missing is very simple, but I do not know what is it or where to focus to solve it.
Using useEffect to track stateful variable changes is more efficient - it avoids unnecessary calls by only executing the code in the callback when the value changes, rather than on every render.
In the case of document.title, it doesn't really matter, since that's an inexpensive operation. But if it was runExpensiveFunction, then this approach:
const UseEffectBasics = () => {
const [value, setVal] = useState(0);
runExpensiveOperation(value);
would be problematic, since the expensive operation would run every time the component re-renders. Putting the code inside a useEffect with a [value] dependency array ensures it only runs when needed - when the value changes.
This is especially important when API calls that result from state changes are involved, which is pretty common. You don't want to call the API every time the component re-renders - you only want to call it when you need to request new data, so putting the API call in a useEffect is a better approach than putting the API call in the main component function body.

Reading component state just after setting when using useState hook in react

This console.log is not working: It'll just print the previous state value as set is async.
const SomeCompo = () => {
const [count, set] = useState(0);
const setFun = () => {
console.log(count);
set(count + 1);
console.log(count);
}
return <button onClick={setFun}>count: {count}</button>
}
I had to read the count in the render itself:
const SomeCompo = () => {
const [count, set] = useState(0);
console.log(count);
const setFun = () => {
set(count + 1);
}
return <button onClick={setFun}>count: {count}</button>
}
Is there a better way to read the value as I don't want to console for every render.
You can use useEffect for this,
useEffect(() => {
console.log(count);
}, [count]) //[count] is a dependency array, useEffect will run only when count changes.
I would suggest not to use setInterval. I would do something like useEffect. This function will be called each time you do a setState. Just like you had callback after setState. Pass the count state in the array, it will watch only for the count change in the state and console your count.
useEffect(() => {
console.log(count);
}, [count]);
Also if you dont need to rerender your other components, you might wanan use useMemo and useCallback. https://www.youtube.com/watch?v=-Ls48dd-vJE
Here to more read: https://reactjs.org/docs/hooks-effect.html
The way to get a state value is to use useEffect and use the state as a dependency. This means that when we change a value the render cycle will finish and a new one will start, then useEffect will trigger:
useEffect( () => { console.log(value); }, [value] );
If you would need to read the value in the same cycle as it is changed a possibility could be to use the useState set function. This shows the latest value just before updating it:
setValue( latest_value => {
const new_value = latest_value + 1;
console.log(new_value);
return new_value;
} );

Resources