Home > OS >  How to setState with select element in react - WITHOUT LAG
How to setState with select element in react - WITHOUT LAG

Time:01-03

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.

  • Related