Home > Back-end >  React init state array with init function that repeatedly calls setState
React init state array with init function that repeatedly calls setState

Time:04-11

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);
  }, []);

View demo on codepen

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

  • Related