Hi, I’m having some trouble. I thought I understood useState pretty well, but now it is behaving in an unexpected way, and I don’t quite know what to make of it.
In addItem(), When I update arr, then set the selected item, it selects the second-to-last item. I assume this is because arr has not yet been updated when I call setSelected()? I don’t know the best way to fix this. I thought about using useEffect(), and selecting the last item whenever arr.length changes. Any suggestions? Thank you so much.
I’m still confused about useState, however. I feel like there is something important I am not understanding. Do you think you (or anyone) could explain why arr has not updated by the time I call setSelected?
It’s not redux, it’s just react. If you have even slightly complex state that’s interdependent, then you would generally use useReducer instead of useState, that’s what it’s there for.
I assume that the setter returned by useState is similar to how setState works. It is asynchronous. You’ve tried to apply await, but that works on promises. Those state setters do not return a promise, they don’t return anything. So, your await is just telling it to wait until that code is run, not to wait for its asynchronous actions to be finished.
In addition to Dan’s suggestion, you could use setSelected before calling, based on the current state of arr.
Furthermore, if selected is always the last element or arr, does it need to be kept in state? Can you just grab the last element when you need it?
Also, another way would be to have a useEffect that listens to arr and is triggered by changes to it, so you set arr in addItem and then setSelected is run based on that effect.
Another option would be to put setSelected inside the callback for setArr, so you are basing it on prev. I don’t know if that is kosher or not, but conceptually it seems to make sense.
Yeah that seems to be what’s happening atm. You’ve introduced a race condition. Async/await isn’t just for making things happen in order, it’s specifically for handling async code that returns promises, which as @kevinSmith says, is not what the setter function returns (it’s async, but returns void).
Without the async/await the two updates should be batched, which will again cause similar behaviour. React will try to optimise things, and because you’re setting it to the length of the variable arr, it’s going to use that value, as it is, rather than the value in next render.
The obvious way to do this is to just give the set[Arr|Selected] functions the correct data at first. Example:
Edit I modified a few of things while I was trying to get stuff running – apologies if there’s anything slightly confusing there. But use the index for the selection, it’s miles easier and will always work fine
So is it pointless to use async/await with useState?
The code I gave is just a stripped-down version of the problem I’m having with a project. In my project, I need selected to be pointing to an object, since I am accessing the data from it. Would you recommend I have one piece of state holding the object, and another one holding the index of the selected item? I didn’t do this because I thought it would just be unnecessary code.
Would there be any possible side-effects from doing this?
Yes. It’s for running asynchronous logic, but not in this case.
I assumed that was the case. It depends entirely on what you’re doing. Is this a to-do app by any chance?
I would strongly suggest using useReducer for the object manipulation over useState.
Also, some of the issues will go away when you break it down into components and use props to pass values in.
Just using the variables existing in the scope of the function? No. The components are just functions. Props are just like normal arguments you pass to a function. State values are also like normal arguments, but effectively allow you to modify the arguments from within the function, forcing the function to run again with the new values.
You still need to be comfortable with useState and there are cases where that makes sense, and it certainly could work here, but I also agree that useReducer would be a cleaner and more sophisticated solution. If you’ve done the FCC stuff on redux reducers, you’ll have a leg up.