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;
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 theset
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.