Search code examples
reactjsaxiosfetchdropdown

How do you render a dropdown list from data table obtained via axios in React?


I feel like this should be simple, but I've been struggling with this and would appreciate some help.

I'm trying to generate a drowdown list from a field in a data table I fetched using Axios. The data table loads and renders just fine, but React does not recognize the list as an array and therefore can not map the items. Both the data table and list are generated and stored in state in the same function at the same time.

Below is my code. Any suggestions on how to do this is much appreciated. Thanks:

import React, {useState, useEffect} from 'react'
import db from './db.js' //<--I have my axios db connection in a seperate file

function App() {

  const [industryData, setIndustryData] = useState([]) //
  const [industryList, setIndustryList] = useState([]) //<--This is the list I want

  const getData= async(req, res)=>{
      const response = await db.get('/getTable/industries')
      const data =  response.data;
      console.log(data) // <--Data loads fine
      setIndustryData(data) // <--Data renders fine

      //I want to get a dropdown list from one of the fields in the data above
      let list = []
      data.forEach(row=>{
        list.push(row.industry)
      })
      console.log(list) //<--this works

      const industries = new Set(list)
      console.log(industries) //<---this works

      setIndustryList(industries) 
      console.log(industryList) //<--This is empty.
   }

  useEffect(()=>{
    getData()
  },[])


    return (
      <div className="App">
      
        //This is the dropdown I want but it's not working
        <select>
          {industryList.map(item=>(  //<---React doesn't recognize this object
            <option key={industryList.indexOf(item)}>{item}</option>
          ))}
        </select>

  
     //But rendering the original data table works.  But I don't want to do this because I don't get unique values.  I need a set.  Also it's just cleaner to use a list than an entire data set.
         <select>
            {industryData.map(item=>( //<---This works fine
              <option key={industryData.indexOf(item)}>{item.industry}</option>
            ))}
         </select>
    </div>
  );

}

export default App;

Solution

  • So there are two problems at play here. Let's first start with the root problem:

    <select>
      {industryList.map(item=>(  //<---React doesn't recognize this object
        <option key={industryList.indexOf(item)}>{item}</option>
      ))}
    </select>
    

    The reason map() does not work is because industryList is a Set.

    const industries = new Set(list)
    setIndustryList(industries)
    

    Set objects do not have a map() method. (They also do not have an indexOf() method.)

    If you aren't using any Set features (apart from it removing duplicate values as part of the constructor). The easiest fix would be to convert industries to an array before setting it as state.

    This can be done with Array.from(industries) or spreading the contents into an array [...industries].

    setIndustryList(Array.from(industries))
    

    If you are using some of the set features you'll want to do this same conversion, but during rendering instead.

    <select>
      {Array.from(industryList).map((item, index) => (
        <option key={index}>{item}</option>
      ))}
    </select>
    

    Array.from() also allows us to pass a second argument mapFn. This does the same as map() would do, but does it during the Set to Array conversion. Causing one less iteration.

    <select>
      {Array.from(industryList, (item, index) => (
        <option key={index}>{item}</option>
      ))}
    </select>
    

    Now, let's move on to the second problem that arose when you where trying to debug the above.

    setIndustryList(industries) 
    console.log(industryList) //<--This is empty.
    

    Why is industryList empty?

    Let's look at the useState documentation for this one:

    Caveats 🔗

    • The set function only updates the state variable for the next render. If you read the state variable after calling the set function, you will still get the old value that was on the screen before your call.

    When you log industryList directly after using setIndustryList(), it does not yet hold its new value.