Why react hook value is not updated in async function? [duplicate] - javascript

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 3 years ago.
When running the run function, I expect that value variable has value 'new', but since even 500 ms, it still remains 'old'. Why that happens and how coud this issue be solved?
import React, { Component, useState } from "react";
import { render } from "react-dom";
function App() {
const [value, setValue] = useState('old');
const run = async() => {
setValue('new')
const data = await wait(500)
console.log(value)
}
return (
<button onClick={run}>
Run
</button>
);
}
render(<App />, document.getElementById("root"));
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}

setState runs asynchronously so it is not immediately reflected in the function code block. You can try using useEffect to watch changes of your state.
useEffect(() => console.log('value', value), [value])

In your case const [value, setValue] = useState('old'); setValue is nothing but same like setState in class Components. So since setState and setValue is asynchronous functions, you can never predict when the values will get updated.
So either you can use componentDidUpdate in class components or useEffect like useEffect(() => callFn();, [value]) . so here callFn() is afunction which will be called after value has been updated.
hope it helps. feel free for doubts

Ok, here is what I think is happenning:
You click the button, which calls your run function - then the run function calls setState.
I think setState is supposed to cause a rerender of the component
with the new value which would be shown immediately if you were displaying it somewhere. So if you want to see immediate changes, add a display.
When you console log in the run function immediately after setState, the component has not been rerendered which means you are still looking at the old value.
When the component rerenders, you are not seeing the console logged value because the run function has not been called again. Clicking the button twice should console log the new value because by then the component has rerendered with that new value.

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 is React state not updated inside functions? [duplicate]

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

Asynchronous way to get the latest change in State with React Hooks

I have started learning React and developing an application using ReactJS. Recently i have moved to React Hooks. I know that in Class Component we can get the latest data from the state with the 2nd argument which is present in setState() like
state = {name: ''};
this.setState({name: name}, () => {console.log(this.state)});
I wanted to know if there is any arguments in React Hooks with which we can get the latest data from it.
I am using React Hooks in the below mentioned way, but upon console logging it always return the previous state
Hooks Eg:
const [socialData, setSocialData] = useState([
{ id: new Date().getTime().toString(), ...newItem }
]);
const onChangeCallback = (index, type, data) => {
let newSocialData = [...socialData];
newSocialData[index] = { ...newSocialData[index], [type]: data };
setSocialData(newSocialData);
onInputChange(newSocialData, formKey);
console.log(newSocialData);
};
The this.setState() second argument is not exactly to get the latest data from the state, but to run some code after the state has been effectively changed.
Remember that setting the state is an asynchronous operation. It is because React needs to wait for other potential state change requests so it can optimize changes and perform them in a single DOM update.
With this.setState() you can pass a function as the first argument, which will receive the latest known state value and should return the new state value.
this.setState((previousState) => {
const newState = previousState + 1;
return newState;
}, () => {
console.log('State has been updated!')
});
With that being said, there are special and rare cases when you need to know exactly when the state change has taken place. In many years of working with React I only faced this scenario once and I consider it a desperate attempt to make things work.
Usually, during a callback execution, like your onChangeCallback you want to change the state as the last thing your function does. If you already know the new state value, why do you want to wait for the real state to change to use it?
The new state value should be a problem to be handled during the next render.
If you want to run some code only when that particular state value changes you can do something like this:
import React, {useState, useEffect, useCallback} from 'react';
function MyComponent() {
const [value, setValue] = useState(false);
const onChangeHandler = useCallback((e) => {
setValue(!!e.target.checked);
}, []);
useEffect(() => {
// THIS WILL RUN ONLY WHEN value CHANGES. VERY SIMILAR TO WHAT YOU ARE TRYING TO DO WITH THE this.setState SECOND ARGUMENT.
}, [value]);
return (
<input type='checkbox' onChange={onChangeHandler} />
);
}
There is also a way to create a custom useState hook, to allow you passing a second argument to setValue and mimic the behavior of this.setState, but internally it would do exactly what I did in the above component. Please let me know if you have any doubt.

Do functions get the latest state value in React?

I have a function inside of my functional component that uses a value saved in state. However, when it is called, it has the original value in state, not the updated value. When I look at my component in Chrome React Dev Tools, I see that the updated value is stored in state. Aren't functions supposed to get the latest state value in React? I didn't think I'd have to wrap my functions in a useEffect every time some value in state they depend on changes. Why is this happening?
const Editor = (props) => {
const [template, setTemplate] = useState(null);
const [openDialog, setOpenDialog] = useState(false);
useEffect(() => {
if (props.templateId) {
getTemplate(props.templateId));
}
},[]);
const getTemplate = (templateId) => {
{...make API to get template...}
.then((response) => {
if (response.template) setTemplate(response.template);
});
}
/* THIS FUNCTION SAYS TEMPLATE IS ALWAYS NULL */
const sendClick = async () => {
if (template) {
await updateTemplate();
} else {
await initializeTemplate();
}
setOpenDialog(true);
};
}
UPDATE: I figured out the issue. The sendClick function is being used inside an object that I have in state. When that object is created, it creates a version of the sendClick function based on the state at that time. I realized I needed to refactor my code so that the function is not stored within my object in state so that the function will always have the latest state values.
Please correct the code there its setTemplate(template)); not getTemplate(template));
I'm guessing that you have that right in the source code... if Yes then,
You have got into a trap that all developers new to React fall into.
This code is betraying you ...
useEffect(() => {
if (props.template) {
setTemplate(template)); // Mentioned as getTemplate(template));
}
},[]); // Here is where you make the mistake
The second argument you pass to the useEffect is called as Dependencies. Meaning if your useEffect is dependent on any state or any variable or function, Ii should be pass as the second argument inside the []. By now you should have got the answer.
Clearly, your useEffect is dependent on template. You should pass that inside the [].
So the code will be : -
useEffect(() => {
if (props.template) {
setTemplate(template)); // Mentioned as getTemplate(template));
}
},[template]);
Now React will automatically run the function every time the value of template changes therefore, updates template.
For more information about useEffect ...
Refer React Documentation
Refer the useEffect API

Should I use state at all for a variable that does not affect rendering?

In the code below, how do I make the value of status variable change immediately after setStatus(!status)? I understand that state updates in React are batched, therefore I am using the useEffect with callback, but still the value of status is logged as false upon first button click (as if it did not change immediately).
Considering that the app rendering is not in any way dependent on the status variable (actually, I would even prefer that the changes of this variable do not trigger a render), would it be a bad practice to not use useEffect at all and make status a "normal" variable instead, assigned like status = true?
function App() {
const [status, setStatus] = React.useState(false)
React.useEffect(() => toggleStatus, [status])
function onClickHandler() {
toggleStatus()
console.log(status)
}
function toggleStatus() {
setStatus(!status)
}
return (
<div>
<button onClick={onClickHandler}>Toggle & log status</button>
</div>
)
}
ReactDOM.render( < App /> , document.getElementById('root'))
<script src="https://unpkg.com/react#^16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.13.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone#6.26.0/babel.js"></script>
<div id="root"></div>
When you click onClickHandler triggered ==> toggleStatus and console.log(status) are triggered synchronously (setStatus(!status) of toggleStatus is triggered asynchronously. So you see false is logged.
AND you are using useEffect incorrectly. React.useEffect(() => toggleStatus, [status]) mean the first argument of useEffect return toggleStatus function, that is the cleanup action of useEffect
Try this to see status logged
React.useEffect(() =>{
//you can see true after first click here
console.log(status)
}, [status])
If you want to change status immediately and don't re-render the component, you would use useRef instead
I will comment your code
function App() {
const [status, setStatus] = React.useState(false)
React.useEffect(() => toggleStatus, [status]) // here you are toggle the status again when the status change
function onClickHandler() {
toggleStatus()
console.log(status)
}
function toggleStatus() {
setStatus(!status) // here you can do setStatus(prevStatus => !prevStatus)
}
return (
<div>
<button onClick={onClickHandler}>Toggle & log status</button>
</div>
)
}
ReactDOM.render( < App /> , document.getElementById('root'))
<script src="https://unpkg.com/react#^16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.13.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone#6.26.0/babel.js"></script>
<div id="root"></div>
You can try with a console.log(status) inside the useEffect and see if everything its ok.
React.useEffect(() => console.log(status), [status])
Now talking about the second point, in my opinion that is not a bad practice. According to React docs:
The state contains data specific to this component that may change
over time. [...] If you don't use it in render(), it shouldn't be in
the state. For example, you can put timer IDs directly on the
instance.
React states are used to track application state and re-render the app, when this underlying state changes.
Now, if you do not want a re-render, when status changes, you should declare status as a regular variable.
You can still use the useEffect react hook, if you want. And you can initialize status here. useEffect is called the component is mounted/rendered on the browser DOM
In the code below, how do I make the value of status variable change immediately after setStatus(!status)?
You can't make state to change immediately as changing state is async.
I would even prefer that the changes of this variable do not trigger a render), would it be bad practice to not use useEffect at all and make status a "normal" variable instead, assigned like status = true?
First, check what are the use cases for useEffect.
About making a "normal" variable instead, its indeed a bad practice:
const Component = () => {
let variable = ...;
return <>...</>
}
That's because, on every render, such variables will re-assigned (because the body of the function component executed on every render). Usually, it is not the desired behavior.
Instead, you want to use a reference with useRef. See what is the difference between useRef and a variable.
Changing the reference value won't trigger a render, therefore won't update the UI.

Categories

Resources