Reputation: 17
So I'm trying fetch data with axios, process it and then render as an option in React select. I will put bellow sections of my code to make it easier to understand.
const [data1, setData1] = useState([]);
const [data2, setData2] = useState([]);
const [options, setOptions] = useState([]);
useEffect(() => {
axios.get(url, {
.then(response => {
if (response.status == 200) {
setData1(response.data);
}
})
.catch((response) => {
});
axios.get(url, {
.then(response => {
if (response.status == 200) {
setData2(response.data);
}
})
.catch((response) => {
});
data1.map(info1 => {
let y = data2.filter(info2 => {
return info1.id == info2.id
})
let z = y.map(user => {
return { value: user, label: user.username }
});
setOptions(y);
})
}, []);
And this is the rendering
<select value={selectedOption} onChange={handleChange} className='w-40'>
{options.map((option) => {
return (
<option value={option.value}>
{option.label}
</option>)
})}
</select>
But it's just giving me an empty select. I understand that my useEffect will run a single time after the render it's done and I should use async/await somehow but I don't understand where. I used const state [render, setReder] for conditional rendering but still won't work.
How should I use async/await?
Thank you for your time.
Upvotes: 1
Views: 6770
Reputation: 2535
The proper way to do conditional rendering in React is pretty simple.
Usually involves checking some value and basing the render upon the result.
Example:
if (options.length === 0) {
return <></>;
}
return (
<select value={selectedOption} onChange={handleChange} className='w-40'>
{options.map((option) => (
<option value={option.value}>
{option.label}
</option>)
}))
</select>
)
Or it can also be inlined.
Like so:
return (
{options.length === 0 ? (
<></>
) : (
<select value={selectedOption} onChange={handleChange} className='w-40'>
{options.map((option) => (
<option value={option.value}>
{option.label}
</option>)
}))
</select>
))
Note: This example is based on checking the value of options
, the same method can be used for other states and variables as-well.
Also, what you are trying to achieve here will likely not work since setState
in React acts like an async
function.
Meaning that by the time you try to map over the value of data1
in the useEffect
, data1
has likely not finished updating yet and still holds the previous value.
In order to do this properly in this case you can simply map over the value of response.data
instead.
Example:
useEffect(() => {
axios.get(url, {
.then(response => {
if (response.status == 200) {
setData1(response.data);
response.data.map(info1 => {
let y = data2.filter(info2 => {
return info1.id == info2.id
})
// Side-note: I don't understand why `z` is needed here if not used.
let z = y.map(user => {
return { value: user, label: user.username }
});
setOptions(y);
})
}
})
.catch((response) => {
});
axios.get(url, {
.then(response => {
if (response.status == 200) {
setData2(response.data);
}
})
.catch((response) => {
});
}, []);
PS: see if you even need data1
/ data2
in this case.
I would suggest to just try and make it a single object, etc..
Upvotes: 0
Reputation: 501
You can use async/await in your code as follows
const [data1, setData1] = useState([]);
const [data2, setData2] = useState([]);
const [options, setOptions] = useState([]);
const fetchData = async() => {
const response1 = axios.get(url)
if (response1.status == 200) {
setData1(response1.data);
}
const response2 = axios.get(url)
if (response2.status == 200) {
setData2(response2.data);
}
}
useEffect(() => {
fetchData();
}, []);
useEffect(()=>{
if(data1.length > 0 && data2.length > 0){
data1.map(info1 => {
let y = data2.filter(info2 => {
return info1.id == info2.id
})
let z = y.map(user => {
return { value: user, label: user.username }
});
setOptions(z);
})
}
},[data1, data2])
And in the render function
{options.length > 0 && <select value={selectedOption} onChange={handleChange}
className='w-40'>
{options.map((option) => {
return (
<option value={option.value}>
{option.label}
</option>)
})}
</select>}
Upvotes: 0
Reputation: 181
I think it's better to use another useEffect with data1 and data2 dependencies. So when data1 and data2 change, this function will be called and inside it, you can calculate options.
useEffect(()=>{
data1.map(info1 => {
let y = data2.filter(info2 => {
return info1.id == info2.id
})
let z = y.map(user => {
return { value: user, label: user.username }
});
setOptions(z);
})
}, [data1, data2])
and also in the rendering part, you should check if the options data is available, render it. otherwise, show loading or something else.
return (
{options && options.length === 0 ? (
<Loading />
) : (
<select value={selectedOption} onChange={handleChange} className='w-40'>
{options.map((option) => (
<option value={option.value}>
{option.label}
</option>)
}))
</select>
))
also for parallel request, I think it's better to use promise.all
like this example:
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
Promise.all([getUserAccount(), getUserPermissions()])
.then(function (results) {
const acct = results[0];
const perm = results[1];
});
Upvotes: 0
Reputation: 11328
Personally, I think you should split your useEffect
in two:
const [data1, setData1] = useState([]);
const [data2, setData2] = useState([]);
const [options, setOptions] = useState([]);
useEffect(() => {
axios.get(url, {
.then(response => {
if (response.status == 200) {
setData1(response.data);
}
})
.catch((response) => {
});
axios.get(url, {
.then(response => {
if (response.status == 200) {
setData2(response.data);
}
})
.catch((response) => {
});
}, []);
useEffect(() => {
data1.map(info1 => {
let y = data2.filter(info2 => {
return info1.id == info2.id
})
let z = y.map(user => {
return { value: user, label: user.username }
});
setOptions(z);
});
}, [data1, data2]);
This way, the options
are updated whenever data1
or data2
are modified.
Updating options
will trigger a re-render of your select options.
From a stylistic point of view, data1.map
should probably be data1.forEach
and keep in mind that your current loop assumes data1
only contains a single entry.
I don't know what sort of data you're dealing with, but this might be more accurate:
const newOptions = [];
data1.forEach(info1 => {
let y = data2.filter(info2 => {
return info1.id == info2.id
})
let z = y.map(user => {
return { value: user, label: user.username }
});
newOptions.push(...z);
});
setOptions(newOptions);
Upvotes: 0