There seems to be a lag of one render cycle when I change a select element and when it's state actually changes. I know that there are several similar questions on this but none of them see to work for me. useEffect is still showing the old state from one render cycle before. Anyone know how to address this?
Parent component code:
import React, {useCallback, useEffect, useState} from 'react'
import Dropdown from '../components/Dropdown.js'
const ValueCalculation = () => {
const industryData = require('../../../backend/standard_data/industry_benchmarks.json')
var industryList = []
industryData.map((record)=>{
var x = record.Industry;
industryList.push(x)
})
const [industry, setIndustry] = useState(industryList[8]);
const updateIndustry = (updatedValue)=>{
console.log(updatedValue); //<--This is updated with the right value!
setIndustry(updatedValue) //<--This is NOT updating the current value
console.log(industry); //<-- This does NOT show the updated value
}
useEffect(()=>{
console.log(industry); //<--Still showing value from previous render cycle
},[])
return (
<div>
<Dropdown
label="Industry"
value={industry}
onChange={(e)=>updateIndustry(e.target.value)}
list={industryList}
/>
</div>
)
}
export default ValueCalculation
Code for Child Dropdown component..
import React from 'react'
const Dropdown = (props) => {
return (
<div className="form-group mb-3">
<label>{props.label}</label>
<select
className="form-control"
value={props.value}
onChange={props.onChange}
>
{
props.list.map(item=>(
<option key={props.list.indexOf(item)} value={item}>{item}</option>
))
}
</select>
</div>
)
}
export default Dropdown
CodePudding user response:
SetState
is async so your console.log
is going to run before the state has been set. The code you have works correctly as you can see in the sandbox link provided.
const updateIndustry = (updatedValue) => {
//This is updated with the right value!
console.log(updatedValue);
//This is updating correctly and will show on re render
setIndustry(updatedValue);
//This will not work since setState is async
//Console.log() is firing before the state has been set
console.log(industry);
};
As for the useEffect. You will need to add industry
as a dependency so that the console.log
is called as soon as the state changes :
useEffect(() => {
console.log(industry);
}, [industry]);
Sandbox : https://codesandbox.io/s/hidden-voice-8jvt2f?file=/src/App.js
CodePudding user response:
So, it's a little bit complicated but your state is changing on every rerender cycle, so ur state it's updated after the updateIndustry it's finished (popped out from js callstack). I tested your code, and it is working perfectly and i refactored it a little bit
import React, { useEffect, useState } from "react";
import Dropdown from "./Dropdown.js";
const App = () => {
var industryList = ["a", "b", "c", "d"];
const [industry, setIndustry] = useState(industryList[0]);
useEffect(() => {
console.log(industry);
}, [industry]);
return (
<div>
<Dropdown
label="Industry"
value={industry}
onChange={(e) => setIndustry(e.target.value)}
list={industryList}
/>
</div>
);
};
export default App;
Also, useEffect hook is reexecuted when its dependency changes value, in your case your dependency array is empty so I added [industry] to it.