I’m trying to update a select with the react-select
custom component for React, due to the race condition that is happening when fetching data, I’m not able to populate the selected value for it. I’ve looked several SO questions on how to handle fetch calls in useEffect
and how to handle them and everything but nothing has worked… Not sure if this is related to a race condition or promises in the fetch
API that I’m not handling properly.
AlergiaForm.component.tsx
import Select, { SingleValue} from "react-select";
import {useState, useEffect} from "react";
interface IOption {
value: string | null;
label: string;
icon?: string;
}
interface IProduct {
id: number,
title: string
}
const AlergiaForm = () => {
const[products, setProducts] = useState(Array<IProduct>);
const[product, setProduct] = useState<SingleValue<IOption> | null>(null);
const[isLoading, setLoading] = useState(false);
const[value, setValue] = useState<string | null | undefined>("");
//Mapper
const mapper = ():IOption[] => {
return products.map(item => {
return { value: item.id, label: item.title};
}) as unknown as IOption[];
}
useEffect(() => {
//Get product, like an edit
const getProduct = () => {
setLoading(true);
fetch('https://dummyjson.com/products/6')
.then(res => res.json())
.then((data) => {
//We get the products dropdown values and we are waiting for it... is not even async and I'm waiting on the promise I think?
getProducts();
setLoading(false);
//Logic to "set" the current value, but since it has a race condition, it's not picking it up
//Works when a hot module refresh happens in Vite because the context is updated already... but not on page "load" or react-router-dom navigation
const product = data as IProduct;
const {id} = {...product};
const selectedProduct = products.find(i => i.id === id) as IProduct;
const ioption = { value: selectedProduct?.id.toString(), label: selectedProduct?.title } as SingleValue<IOption>;
setProduct(ioption);
});
}
const getProducts = () => {
fetch('https://dummyjson.com/products')
.then(res => res.json())
.then((data) => {
const products = data.products as IProduct[];
setProducts(products);
});
}
getProduct();
}, []);
const handleDropDownChange = (selectedOption: SingleValue<IOption>):void => {
const { value } = {...selectedOption};
setValue(value);
}
return(
<>
<Select options={mapper()} value={isLoading ? null : product} onChange={handleDropDownChange} />
<p>Value selected: {product?.value ?? value}</p>
<p>Is Loading: {isLoading ? "Loading..." : "Done"}</p>
</>
)
}
export default AlergiaForm;
App.tsx
import AlergiaForm from './AlergiaForm.component'
function App() {
return (
<main>
<AlergiaForm />
</main>
)
}
export default App;
Minimal example with the same code:
Example
You will see it does not update the selected value when the product is retrieved, and that’s probably because the promise is not done when trying to set the product, but not sure how to avoid that even with waiting for the actual response to get the data and set the values correctly.
Other thing to mention is that I’m using Vite
, everytime it reloads the page on a save file the value is selected, so I know the code is working, it’s just I guess the issue is the race condition here.
Articles read: https://maxrozen.com/fetching-data-react-with-useeffect
https://overreacted.io/a-complete-guide-to-useeffect/
2
Answers
Example
The product id and the product list is what’s needed to fetch, so that should be your states. And since the fetching of those are unrelated, they should be separate
useEffect
s. Once both are fetched, you canfind
the product. Since the product is completely dependent on two states, it shouldn’t be it’s own state.