This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
This is a popular question among all the new react developers but somehow I'm not able to understand the logic behind available solutions. I'm trying to update the state variable using hooks and trying it read the updated value but always it returns a previous value instead of new value. Below is the sequence of my code execution.
onClick={setTransactionAccountId}
on button click, it executes the below code and updates the state but the console.log shows the old value.
const [accountId, setAccountId] = useState(0);
const setTransactionAccountId = e => {
console.log("Clicked ID:", e.currentTarget.value);
setAccountId(e.currentTarget.value);
console.log("accountId:", accountId);
};
console log:
first button click:
Clicked ID: 0
accountId: 0
second button click:
Clicked ID: 1
accountId: 0
could anyone please tell me the reason behind this behaviour and how to tackle it.
accountId won't have been updated this render. You have to wait for the next render for it to be updated. accountId only gets populated at the top of the function component when useState is called. You're in the middle of the render. If you need the actual value, keep pulling it out of e.currentTarget.value.
From react docs
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
State updates may be batched and updated asynchronously, therefore the state may have not updated when console.log() is called. You will get the guaranteed updated result in your next call to useEffect hook.
This is because setState is asynchronous. Executing console.log('accountId:', accountId) before the state has updated will still give you the previous state value. If you add an async/await, that should fix the issue.
const setTransactionAccountId = async (e) => {
console.log('Clicked ID:', e.currentTarget.value)
await setAccountId(e.currentTarget.value)
console.log('accountId:', accountId)
}
Related
This question already has answers here:
React - useState - why setTimeout function does not have latest state value?
(2 answers)
Closed 7 months ago.
I have a component that renders a table with objects. This component shows a button that, when pressed, sends the parent a specific object. The parent must set it in the state to display some graphical stuff. The rendering is working correctly, what I don't understand is why I am getting an outdated value after setting the state correctly.
It's not a race condition, React is simply ignoring the updated value of a variable, even when it re-renders the component correctly.
A minimal example:
import { useState } from "react";
import { SomeComponent } from "./SomeComponent";
export default function App() {
const [currentID, setCurrentID] = useState(null);
function getData() {
console.log("Getting data of: ", currentID); // PROBLEM: this is null
}
function setAndRetrieveData(value) {
setCurrentID(value);
// Just to show the problem and discard race conditions.
setTimeout(() => {
getData();
}, 1500);
}
return (
<div className="App">
<h1>Current ID: {currentID}</h1> {/* This works fine */}
<SomeComponent getInfoFor={setAndRetrieveData} />
</div>
);
}
SomeComponent:
export function SomeComponent(props) {
const randomID = 45;
return <button onClick={() => props.getInfoFor(randomID)}>Get info</button>;
}
Even with solutions like useStateCallback the problem persists.
Is there a way to do this without having to use the awful useEffect which is not clear when reading the code? Because the logic of the system is "when this button is pressed, make a request to obtain the information", using the hook useEffect the logic becomes "when the value of currentID changes make a request", if at some point I want to change the state of that variable and perform another action that is not to obtain the data from the server then I will be in trouble.
Thanks in advance
I think this is an issue with the way Javascript closures work.
When you execute a function, it gets bundled with all the data that pertains to it and then gets executed.
The issue is that you call this:
setTimeout(() => {
getData();
}, 1500);
inside setAndRetrieveData(value).
Even though it's inside a setTimeout, the getData() function has been bundled with the information it needs (currentID) at that point in time, not when it actually runs. So it gets bundled with the currentId before the state update takes place
Unfortunately, I would recommend using useEffect. This is the best way to ensure you avoid issues like this and any potential race conditions. Hopefully someone else can provide a different approach!
when setAndRetrieveData is called it sets a state that leads to the component being rerendered to reflect the new state. When the timeout finishes The function getData was created in the previous render. And thus only has access to the state variable from the previous render. That now is undefined.
what you could try is using a useEffect hook that that listens to changes of
currentID.
useEffect(() => {
const timeoutId = setTimeout(() => {
// Do something with the updated value
},1000);
return () => {
// if the data updates prematurely
// we cancel the timeout and start a new one
clearTimeout(timeoutId);
}
},[currentID])
While passing a parameter to a cloud function, it always passes the one previous state. I am trying to fetch the image which is currently rendered on a canvas and pass it to a cloud function in another component. Even though I'm using useState and useEffect I am unable to get the immediate canvas state. It always passes the image that was one prior to current state.
I assume this is happening because the value is captured then the component having the cloud function renders. And subsequent change to the canvas would require a re-render of the cloud function component in order for it to capture the latest value?
I am getting the base64 value from the canvas of one component like this:
let imageToSave = new Image();
imageToSave.src = canvas.current.toDataURL('image/png', 1.0)
return imageToSave.src
}
I wish to pass it as a parameter in a cloud function call in another component. For which I am doing this:
const [finalImage, setFinalImage] = useState(null)
const { fetch:cloudFetch } = useMoralisCloudFunction(
"handlemint",
{
autoFetch: false,
userAddress: userAddress,
image: finalImage
}
Now I have tried all kinds of ways to setFinalImage(props.saveImage()) to pass the base64 into image parameter before my function call. I've tried useEffect, I have aaded setFinalImage inside a function and tried to update the value BEFORE I call the cloud function...
async function updateFinalImage(image) {
setFinalImage(image)
}
const cloudCall = async ()=>{
const image = await props.saveImage()
await updateFinalImage(image)
await cloudFetch({
onSuccess: (data)=>console.log(data)
})
}
I have tried both regular and async for all this. No matter what I do, the value passed into my image parameter is always one step behind . So if I have a dog picture on canvas, the null value gets passed in. Then when I change dog to cat pic on canvas, the dog (previous picture) gets passed in.
My saveImage function is not lagging. If I console log it in child component it gives me whatever latest value is on the canvas. Somehow while passing into the cloudCall with useState update, always the previous value goes and not the current updated value. I have tried to circumvent the asynchronous nature of useState by putting it inside the function but nothing works. What am I doing wrong?
My question is how do I fetch the base64 from parent component's function saveImage() and pass it in the cloud function as image: finalImage in a way that it takes the LATEST update always when I call the cloud function. Right now trying useState to update the value of image is ALWAYS sending the previous value.
Edit: I found a similar answer here
The key point that applies to my case is this:
Also, the main issue here is not just the asynchronous nature but the fact that state values are used by functions based on their current closures, and state updates will reflect in the next re-render by which the existing closures are not affected, but new ones are created.
However even though I have tried to trigger a re-render using 2 useEffects (one to update the value obtained and another to re-render component on obtaining value), I am still unable to solve this.
SOLVED IT! after 3 days of racking my head!
function saveImage() {
let imageToSave = new Image();
imageToSave.src = canvas.current.toDataURL('image/png', 1.0)
imageToSave.onload= () => {
return imageToSave.src
}
}
I added the return call inside an onload event. This way it ONLY returns AFTER the new (latest) image has loaded on canvas and not prematurely. Doing this solved my problem of premature return, which was the reason my base64 for was always one render behind what was ACTUALLY on canvas.
I have a table with Delete button in each row. When you delete the row, the Client will send request do the API asynchronously. Which means, that if you delete multiple rows quickly, you don't have to wait for one request to finish, to send another Delete request. When you click Delete button and request was send, the button will be disabled (before the response comes back and the row will disappear).
This is the function that handles delete in the Container:
const [deletingTeams, setDeletingTeams] = useState([]);
const handleDelete = (teamId) => {
setDeletingTeams([...deletingTeams, teamId]);
deleteTeam(teamId).then(() => {
console.log(deletingTeams); <---- This prints EMPTY array. Why????
let newState = deletingTeams.filter((id) => id !== teamId);
setDeletingTeams(newState);
});
};
Very simple. deletingTeams is an array that holds ids of teams that are being deleted at the moment (the response didn't comeback from that API, that deletion was successful).
In line 3 (setDeletingTeams([...deletingTeams, teamId]);) I'm adding new teamId to the array, and it works. New array is being passed to the List component and Delete button is indeed disabled. BUT...
...when response comes back, in then promise, I want to remove that teamId from the array. The problem is, that deletingTeams array is already empty (before the filter). Why??
Thanks for the explanation,
State updates are async - you are not guaranteed to see latest value of the updated state straight away.
Use the callback approach to filter and set the state.
const [deletingTeams, setDeletingTeams] = useState([]);
const handleDelete = (teamId) => {
setDeletingTeams([...deletingTeams, teamId]);
deleteTeam(teamId).then(() => {
// console.log(deletingTeams); //<---- state updates are async - you will see the latest value of the state in the next re-render
// let newState = deletingTeams.filter((id) => id !== teamId); //<---- remove this
setDeletingTeams((prev) => prev.filter((id) => id !== teamId)); //<--- use call back approach to set the state
});
};
In short, the reason this is happening is because deletingTeams gets its value before your asynchronous call. The reasons why are subtle!
What's going on is, when you use detetingTeams in the then function for deleteTeam, the callback function you pass in is using a JavaScript feature called function closures. The variable deletingTeams is used in your then function - but it's not defined by that function! So it can't have local function scope. Where exactly is it coming from, then?
The answer is nuanced. The variable is defined in your first line: const [deletingTeams, setDeletingTeams] = useState([]);. At this point, the variable is assigned a value based on your components' current state - the teams you currently have before deleting. Then, you reference it in your function. At this time, the variable deletingTeams becomes added to the then functions' scope. It is a reference to the same variable outside the function!
Ok, so the reason that deletingTeams is an empty array is because it's an empty array when you create the then function, right? Not quite. Any changes to deletingTeams are reflected when you execute the then function. But in your example, deletingTeams never changes its value. Your component's state changes when you call setDeletingTeams, but you only look up the state once - at the beginning of the block of code. There's no magic that will change deletingTeams' value; for that, you have to call useState again.
deletingTeams is given a fresh value each time your component rerenders. But unfortunately, the then function's closure scope is only determined once. (Each time you rerender, you actually generate a new then function, scoped to that render's version of the deletingTeams variable!) The component may have rerendered, but the then block is still called with the same stale value.
So, altogether, here's what's going on:
deletingTeams is assigned a value in your first line: const [deletingTeams, setDeletingTeams] = useState([]);
You update the state with setDeletingTeams, but useState is only called once, so deletingTeams is never updates to match new values.
deletingTeams gets a new value on each new render of your component, but by then, your then function is unfortunately already bound to a specific, past deletingTeams variable that never updates.
So when your then block executes, deletingTeams has a stale value.
So what is the correct approach, then? We can read the state in the then function to get the freshest value of deletingTeams. It's simply this.state.deletingTeams. Here's more information: https://reactjs.org/docs/hooks-state.html#reading-state
(Because you're using arrow functions, the then function automatically uses the same this as the component, so everything should work.)
So this should do the trick:
const [deletingTeams, setDeletingTeams] = useState([]);
const handleDelete = (teamId) => {
setDeletingTeams([...deletingTeams, teamId]);
deleteTeam(teamId).then(() => {
let newState = this.state.deletingTeams.filter((id) => id !== teamId);
setDeletingTeams(newState);
});
};
(Be sure to use this.state.deletingTeams instead of calling useState again in this kind of situation. Calling useState inside of nested functions or callbacks can lead to bugs: https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level)
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),
]);
Please note the question is NOT about Asynchronous Nature of useState. It is Asynchronous and it's well answered here: useState set method not reflecting change immediately Please donot confuse this with that! I have used useEffect to get the button working, as shared in codepen
My question is can i add / include any asynchronous function within main component?
Main Component: HelloWorld
Sub function: interestShown => Can this be asynchronous? in the example i have simplified it as much as possible with console.log output.
Is this an advisable route or am i attempting something bizarre?
I have tried looking for solution to this problem, but I think all answers were directing me to classes, I am looking to get this going in React Functional Component if possible.
I have defined state like so:
const [interested, setInterested] = React.useState(false)
when I call this function:
function interestShown() {
console.log("Before Set State fired", interested)
setInterested(!interested)
console.log("After Set State Fired", interested)
}
The after state change console log needs to show the changed state, I couldn't get this going, I tried many options
My problem is here in this code pen: https://codepen.io/deepeshkr/pen/PowQWwv
The console log output:
pen.js:6 Before Set State fired false
pen.js:8 After Set State Fired false => this should be true, but i am guessing this is asynchronous change, this is my question how to fire this After State Change?
pen.js:14 Fire only it's true true
There are similar sounding questions, but i am not sure they are asking the same thing as i am asking or question is well explained to demonstrate the problem.
Yes, it is asynchronous. So you won't see the changes immediately.
From the setState() documentation:
The setState() function is used to update the state. It accepts a new state value and enqueues a re-render of the component.
You can use useEffect() hook to capture state changes, from the documentation:
Mutations, subscriptions, timers, logging, and other side effects are not allowed inside the main body of a function component (referred to as React’s render phase). Doing so will lead to confusing bugs and inconsistencies in the UI.
You can import it like:
import React, { useState, useEffect } from 'react';
The following code snippet will help you to log interested state changes:
useEffect(() => {
console.log(interested);
}, ['state has changed', interested]);
I hope that clarifies.
I got the way i could do an asynhronous requests with react functional component using effect, posting here so it could help others,
React.useEffect(() => {
if (showReport) {
fetch("https://website.com/json") // Get the data external or internal
.then(res => res.json())
.then(ip => Promise.all([ip, getSomethingElse()])) // Get something else, again external or internal function call
.then(([ip, gp]) => {
setData(gp); // Update the state
setIp(ip);
setShowReport(false);
});
}
}, [showReport]);
Hope this helps others