简体   繁体   中英

React useState() array not updating

I have created new custom selectbox using React. There is pre-populated array which I am loading on component load (using useEffect). When user search for any non existing country there would be a option to add. So I used useState hooked. Code is as under:-

const [countries, setCountries] = useState([]);

Countries list:-

[
  {"value": 1, "label": "Singapore"},
  {"value": 2, "label": "Malaysia"},
  {"value": 3, "label": "Indonesia"},
  {"value": 4, "label": "Phillipines"},
  {"value": 5, "label": "Thailand"},
  {"value": 6, "label": "India"},
  {"value": 7, "label": "Australia"},
  {"value": 8, "label": "Pakistan"}
]


const handleHTTPRequest = () => {
    service.getData() 
    .then(res => {
      setCountries(res);
    })
    .catch((err) => console.error(err))
  }

  useEffect(() => {
    handleHTTPRequest()
  })

I am checking the searched country in the array and if not exists I simply add in array

const addCountry = (country) => {
    let isRecordExist = countries.filter(c => c.label === country).length > 0 ? true : false;
    const obj = {
      value: countries.length + 1,
      label: country
    }
    let updatedVal = [...countries, obj]
    
    setSelectedCountry(country)

    if (!isRecordExist) {
      **setCountries**(updatedVal) // updating array
    }
  }

The problem is its not updating although I can see data in updatedVal.

Entire code is here:-

https://github.com/ananddeepsingh/react-selectbox

The problem appears to be that you're passing to useState() the array ( updatedVal ) that has its reference unchanged, thus it appears to React that your data hasn't been modified and it bails out without updating your state .

Try drop that unnecessary variable and do directly setCountries([...countries, obj])

Another minor fix about your code I may suggest: you may use Array.prototype.every() to make sure that every existing item has different label . It has two advantages over .filter() - it will stop looping right upon hitting duplicate (if one exists) and won't proceed till the end of array (as .filter() does), thus won't slow down unnecessarily re-render and it returns boolean, so you won't actually need extra variable for that.

Following is a quick demo of that approach:

 const { useState, useEffect } = React, { render } = ReactDOM, rootNode = document.getElementById('root') const CountryList = () => { const [countries, setCountries] = useState([]) useEffect(() => { fetch('https://run.mocky.io/v3/40a13c3b-436e-418c-85e3-d3884666ca05').then(res => res.json()).then(data => setCountries(data)) }, []) const addCountry = e => { e.preventDefault() const countryName = new FormData(e.target).get('label') if(countries.every(({label}) => label.= countryName)) setCountries([..,countries: { label, countryName: value. countries.length+1 } ]) e.target.reset() } return.,countries,length && ( <div> <ul> { countries.map(({value, label}) => ( <li key={value}>{label}</li> )) } </ul> <form onSubmit={addCountry}> <input name="label" /> <input type="submit" value="Add country" /> </form> </div> ) } render ( <CountryList />, rootNode )
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

I have found the root problem. In your Select Box component you have:

 const [defaultValueoptions, setdefaultValueoptions] = useState(options);

It should be:

 const [defaultValueoptions, setdefaultValueoptions] = useState([]);

  useEffect(() => {
    setdefaultValueoptions(options);
  }, [options]);

The problem is that you are not updating the countries list inside your SelectBox component. The countries list shouldn't be managed by the App component but by SelectBox component, as it's a stateful component. You are trying to get React to rerender your SelectBox component by changing the state of the App component, which won't work. Well it rerenders because you change a prop options , but it uses defaultValueoptions to display options, which you never update.

Add this to your SelectBox component:

useEffect(() => {
  setDefaultValueOptions(options);
}, [options]);

And change the line 9 to:

const [defaultValueoptions, setDefaultValueOptions] = useState(options);

EDIT I didn't see that Talmacel Marian Silviu had already edited his answer: https://stackoverflow.com/a/63092808/828023

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM