Okay, so I have a function that has a dropdown with category options. Once a category is selected, it will fill in a multi select (react-select) with its corresponding subcategories. The problem I have is when the category is selected, it adds it to my values object using setValues. That works. But during the callback function of the getSubcategories, I want to set the subcategoriesList portion to all the subcategories that get returned. When I do that, it ignores the category. I use JSON.stringify to display it to the web page and I can see the value show up, then disappear. It only happens during my the callback function and setting the subcategorieslist variable.
// My initial state
const initialState = {
category: '',
subcategoriesList: [],
}
// Set initial state
const [values, setValues] = useState(initialState)
// CategoryChange Handler
const handleCategoryChange = (e) => {
setValues({...values, category: e.target.value})
// It's working so far, category is displayed correctly
getSubcategories(e.target.value).then((res) => {
let options = []
res.data.map((option) => {
options.push({value: option._id, label: option.name})
})
// This line below is what gets rid of my category
// Also sets the subcategoriesList correctly
setValues({...values, subcategoriesList: options})
})}
I’m using JSON.stringify(values.category) and it will display the value of category for a split second, then disappear. When trying to enter it into my database I get the error of Cast to ObjectId failed for value "" (type string) at path "category" because of "BSONError"
>Solution :
The values state used here setValues({...values, subcategoriesList: options}) is the original value before you’ve added the category. The new value is added on the next render, and a new values const is created, but the function (closure) that handles the response contains a reference to the old one.
Due to the async nature of React’s state updates (the state is updated on the next render), when you have multiple state changes, and each of them should use the previous updated state, use an updater function:
If you pass a function as nextState, it will be treated as an updater
function. It must be pure, should take the pending state as its only
argument, and should return the next state. React will put your
updater function in a queue and re-render your component. During the
next render, React will calculate the next state by applying all of
the queued updaters to the previous state.
Code:
const handleCategoryChange = (e) => {
setValues(v => ({ ...v, category: e.target.value }))
getSubcategories(e.target.value).then((res) => {
const subcategoriesList = res.data.map(option => (
{value: option._id, label: option.name}
))
setValues(v => ({ ...v, subcategoriesList }))
})}