3

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

5
  • I dont know why you are saying its not working, I have just tried it and it seems to be adding it properly in the list for me. Commented Jul 25, 2020 at 19:09
  • have you tried using my git repo. If possible can you please fork my git repo and test it. Might be I am doing something wrong Commented Jul 25, 2020 at 19:17
  • I cloned you're repo, installed all the dependencies and I have run it with yarn start...When I search for a place it does not exist, I can add it and it appears in the list as required. Commented Jul 25, 2020 at 19:21
  • In my repo. I did some thing wrong to make this works. So, if you go to line no 23 in App.js, I am directly updating array rather than using setCountries method. Please modify in your local and then run Commented Jul 25, 2020 at 19:25
  • The problem is not in App.js, it's in your select-box.js. Give me a minute to take a deeper look. Commented Jul 25, 2020 at 20:06

3 Answers 3

8

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>

Sign up to request clarification or add additional context in comments.

2 Comments

@AnandDeepSingh : check out updated version of my answer, hopefully it gives you a clue
Thanks @Yevgen, it very interesting and thanks for sharing
2

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

2 Comments

hey, I am not sure exactly what the problem is with your code as I did not have time to check all code but when you put a key on an element, everytime that key changes the element re-renders...and it will use the new state...voila! the problem might be better solved other way but in a time of need you do with what you can.
Although this hack works but its not updating the updated value with country object.
1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.