Reputation: 633
I don't know exactly what it is, but I have run into countless problems in trying to do the simplest state updates on arrays using hooks.
The only thing that I have found to work is using the useReducer to perform a single update on the array with putting dispatch on onClick handlers. In my current project, I am trying to update array state in a for loop nested in a function that runs on a form submit. I have tried many different solutions, and this is just one of my attempts.
function sessionToState(session) {
let formattedArray = []
for (let i = 0; i < session.length; i++) {
formattedArray.push({ url: session[i] })
setLinksArray([...linksArray, formattedArray[i]])
}
}
// --------------------------------------------------------
return (
<div>
<form
method="post"
onSubmit={async e => {
e.preventDefault()
const session = await getURLs({ populate: true })
sessionToState(session)
await createGroup()
I was wondering if there are any big things that I am missing, or maybe some great tips and tricks on how to work with arrays using hooks. If any more information is needed don't hesitate to ask. Thanks.
Upvotes: 0
Views: 1828
Reputation: 22324
I was wondering if there are any big things that I am missing
TLDR: setLinksArray
does not update linksArray
in the current render, but in the next render.
Assuming the variables are initialized as follows:
const [linksArray, setLinksArray] = useState([])
A hint is in the const
keyword, linksArray
is a constant within 1 render (and this fact wouldn't change with let
, because it's just how useState
works).
The idea of setLinksArray()
is to make a different constant value in the next render.
So the for
loop would be similar to:
setLinksArray([...[], session0])
setLinksArray([...[], session1])
setLinksArray([...[], session2])
and you would get linksArray = [session2]
in the next render.
Best way to keep sane would be to call any setState
function only once per state per render (you can have multiple states though), smallest change to your code:
function sessionToState(session) {
let formattedArray = []
for (let i = 0; i < session.length; i++) {
formattedArray.push({ url: session[i] })
}
setLinksArray(formattedArray)
}
Furthermore, if you need to perform a side effect (like an API call) after all setState
functions do their jobs, i.e. after the NEXT render, you would need useEffect
:
useEffect(() => {
...do something with updated linksArray...
}, [linksArray])
For a deep dive, see https://overreacted.io/react-as-a-ui-runtime
Upvotes: 2
Reputation: 3901
Performance wise you shouldn't call setState
every iteration. You should set state with final array.
const sessionToState = (session) => {
setLinksArray(
session.map(sessionItem => ({url: sessionItem}))
);
}
... or if you want to keep old items too you should do it with function inside setState ...
const sessionToState = (session) => {
setLinksArray(oldState => [
...oldState,
...session.map(sessionItem => ({url: sessionItem}))
]);
}
Upvotes: 2
Reputation: 16726
When invoking state setter from nested function calls you should use functional update form of setState
. In your case it would be:
setLinksArray(linksArray => [...linksArray, formattedArray[i]])
It is not exactly clear what kind of problems you encounter, but the fix above will save you from unexpected state of linksArray
.
Also this applies to any state, not only arrays.
Upvotes: 2