'React init state array with init function that repeatedly calls setState
I am trying to init a state array by calling an init function multiple times in the useEffect hook.
// id for new Users
const [id, setId] = useState(0);
// users list
const [users, setUsers] = useState([]);
function createUser() {
const newUser = {
id: id,
name: `User-${id}`,
creationDate: new Date().toLocaleString("en-uk")
};
setUsers(oldUsers => {
let users = oldUsers.slice();
users = users.concat(newUser);
return users;
});
setId(oldId => oldId + 1);
}
And then trying to create 3 users
useEffect(() => {
createUser();
createUser();
createUser();
}, []);
I am expecting 3 users will be created with id=0, 1, 2 and the id state is now equals to 3.
However, the output is
User-0 with id=0 was created at 11/04/2022, 15:49:00
User-0 with id=0 was created at 11/04/2022, 15:49:00
User-0 with id=0 was created at 11/04/2022, 15:49:00
Question
Am I missing something here? What should I do to properly initialise the array?
What I thought
My actual init function (createUser in this example) consists of some complex logic. A possible workaround I can think of is doing the createUser logic inside the useEffect hook directly, and call setId at the end. But this style yields duplicate codes.
useEffect(() => {
let _users = [];
for (let i = 0; i < 3; i++) {
const newUser = {
id: i,
name: `User-${i}`,
creationDate: new Date().toLocaleString("en-uk")
};
_users = _users.concat(newUser);
}
setUsers(_users);
setId(3);
}, [])
codepen here
Thanks in advance!
Solution 1:[1]
You are calling createUser function 3 times on initial render.
But state is preserved regardless of createUser state updation.
A better approach would be passing number of required users to the function. Modify the createUser function as:
useEffect(() => {
function createUser(users) {
for (var i = 0; i < users; i++) {
const newUser = {
id: i,
name: `User-${i}`,
creationDate: new Date().toLocaleString("en-uk")
};
setUsers((oldUsers) => [...oldUsers, newUser]);
}
}
createUser(3);
}, []);
Solution 2:[2]
React hooks like useState useEffect are asynchronous and run in batches. All three createUser run in single batch with id=0;
For running complex flows like this, it is recommended to use useReducer
https://reactjs.org/docs/hooks-reference.html#:~:text=useReducer%20is%20usually%20preferable%20to,dispatch%20down%20instead%20of%20callbacks.
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
With useReducer, you can implement desired logic with id and users inside.
However, I do recommend using nanoid for unique ids.
Solution 3:[3]
useState is asynchornous, so that's why whenever you call setId 3 times at once under useEffect, your oldId is always referred to 0. To have a complete fix for your case, we should not call setId even though setUsers multiple times in one code snippet
function createUser(id) {
const newUser = {
id: id,
name: `User-${id}`,
creationDate: new Date().toLocaleString("en-uk")
};
//setUsers(oldUsers => [...oldUsers, newUser]); //should not call it here
//setId(oldId => oldId + 1); //should not call it here
return newUser; //return the new user
}
useEffect(() => {
let newUsers = [];
let currentId = id; //from your state
for (let i = 0; i < 3; i++) {
const newUser = createUser(currentId++);
newUsers.push(newUser)
}
setUsers(oldUsers => [...oldUsers, ...newUsers]); //update current user list
setId(currentId); //update current user id
}, [])
If you have a button to add a new user, you can call it like below
const handleAddNewUser = () => {
const currentId = id;
const newUser = createUser(currentId++);
setUsers(oldUsers => [...oldUsers, newUser]);
setId(currentId); //update current user id
}
You can check this sandbox
Solution 4:[4]
Use this uuid module for id. Id should be unique so you will recieve them from backend api's. And in your scenario when call create function it will call setUser which rerender the component so id remain 0 because of rerendering
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 | |
| Solution 4 | RoshaanAli |
