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!
CodePudding user response:
React hooks like useState
useEffect
are asynchronous and run in batches. All three createUser
run in single butch with id=0;
For running complex flows like this it is recommended to use useReducer
https://reactjs.org/docs/hooks-reference.html#:~:text=useReducer is usually preferable to,dispatch down instead of callbacks.
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.
CodePudding user response:
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);
}, []);
CodePudding user response:
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
CodePudding user response:
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 sanbox