Updating state with nested array of objects without mutation

Viewed 136

I am trying to update my state. I want to be able to update a specific isChecked property when it is updated when a user clicks a checkbox. My state below:

this.state = {
    userPermissions: [
    {
      appName: "Dashboard",
      roles: [
        {
          appId: "1",
          statusId: 1,
          isChecked: false
        },
        {
          appId: "1",
          statusId: 2,
          isChecked: true
        },
      ]
    },
    {
      appName: "Finance",
      roles: [
        {
          appId: "2",
          statusId: 3,
          isChecked: false
        },
        {
          appId: "2",
          statusId: 4,
          isChecked: true
        },
      ]
    }
  ]
}

This is what I have tried. I have to map twice to get to the roles and then set the conditions, however, I am assuming I am mutating the state with another new one so when I render it, it breaks.

handleCheck = (e, data) => {
  const checked = e.target.checked;

  this.setState(prevState => ({
    userPermissions: prevState.userPermissions
      .map(item => item.roles
        .map(item => item.appId === data.appId && item.statusId === data.statusId ? { ...item, isChecked: checked } : item))
      }))
  }

Is there a cleaner way to do this without mutation? Thanks.

3 Answers

your object changes to this after you run it through your maps enter image description here

this.setState((state) => {
  const { userPermissions } = state;
const index = userPermissions.findIndex(e => e.roles.findIndex(ee => ee.appId === "1")!= -1)
const roleIndex = userPermissions[index].roles.findIndex(role => role.appId === "1")
  return {
    ...state,
    userPermissions: [
      ...userPermissions.slice(0, index),
      Object.assign(
        { ...userPermissions.slice[index] },
        {
          roles: [
            ...userPermissions[index].roles.slice(0, roleIndex),
            Object.assign(userPermissions[index].roles[roleIndex], {
              isChecked: !userPermissions[index].roles[roleIndex].isChecked
            }),
            ...userPermissions[index].roles.slice(roleIndex + 1)
          ]
        }
      ),
      ...userPermissions.slice(index + 1)
    ]
  };
});
let state = {
   keys: ['Finance', 'Dashboard'],
   Finance: [
      {
         appId: '2',
         statusId: 3,
         isChecked: false,
      },
      {
         appId: '2',
         statusId: 4,
         isChecked: true,
      },
   ],
   Dashboard: [
      {
         appId: '1',
         statusId: 1,
         isChecked: false,
      },
      {
         appId: '1',
         statusId: 2,
         isChecked: true,
      },
   ],
};

this.setState(prev => {
   let Finance = prev.Finance.map(item =>
      item.appId === data.appId && item.statusId === data.statusId
         ? { ...item, isChecked: checked }
         : item
   );
   let Dashboard = prev.Dashboard.map(item =>
      item.appId === data.appId && item.statusId === data.statusId
         ? { ...item, isChecked: checked }
         : item
   );
   return {
      keys: prev.keys,
      Finance,
      Dashboard,
   };
});

I have changed your state structure,I think it is more readable and you can read the state object and debug it much better

If anyone has issues with this in the future, I have posted the way I have solved this. I feel this is a cleaner solution.

const { userPermissions } = this.state;
const { checked } = e.target;

//find the app index
const appIndex = userPermissions.findIndex(element => element.appName === data.appName);

//find the status index
const roleIndex = userPermissions[appIndex].roles.findIndex(role => role.statusId=== data.statusId);

//create a copy of state
let newArr = [...userPermissions];

//make change on isChecked
newArr[appIndex].roles[roleIndex] = { ...newArr[appIndex].roles[roleIndex], isChecked: checked };

this.setState({
  userPermissions: newArr
})

Related