Update deeply nested object property in immutableJS list

1.6k Views Asked by At

I've a following hierarchy of immutable list:

fromJS({
departments: [{
    departmentName: 'PHP',
    employees: [{
        employeeId: 1000,
        employeeName: 'Manish',
        projects: [{
            projectId: 200,
            projectName: 'ABC'
        },
        {
            projectId: 300,
            projectName: 'DEF'
        }]
    }]
}]
})

In this list, I want to update the project name of project ID 200. Though I can update the simple one level of array of objects by finding their indexes but I don't have any idea that how to begin with this one in immutableJS.

3

There are 3 best solutions below

0
On

I recommend you either not using immutable.js for such complex data structures, or to refactor the structures to simple collections. Otherwise not only you would suffer from maintenance hell, but performance would be hundreds times worse (but still you might not notice it, depends on your app).

0
On

I believe the idiomatic way to do this with Immutable would be to use a lot of .map and .update calls:

const state = fromJS(/*...*/);
const newState = state.update('departments',
  departments => departments.map(
    department => department.update('employees',
      employees => employees.map(
        employee => employee.update('projects',
          projects => projects.map(
            project => project.get('projectId') === 200 ?
            project.set('projectName', 'NEW_PROJECT_NAME') :
            project
          ))))));

const state = Immutable.fromJS({
  departments: [{
    departmentName: 'PHP',
    employees: [{
      employeeId: 1000,
      employeeName: 'Manish',
      projects: [{
          projectId: 200,
          projectName: 'ABC'
        },
        {
          projectId: 300,
          projectName: 'DEF'
        }
      ]
    }]
  }]
})

const newState = state.update('departments',
  departments => departments.map(
    department => department.update('employees',
      employees => employees.map(
        employee => employee.update('projects',
          projects => projects.map(
            project => project.get('projectId') === 200 ?
            project.set('projectName', 'NEW_PROJECT_NAME') :
            project
          ))))));

console.log(newState);
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.min.js"></script>

Dealing with deeply nested data is annoying. I think you could make your life a lot simpler if you flattened your data structures a bit. Take this structure for example:

const state = fromJS({
  departments: [{
    employeeIds: [ 1000 ]
  }],
  employees: {
    1000: {
      employeeId: 1000,
      employeeName: 'Manish',
      projectIds: [ 200, 300 ]
    }
  },
  projects: {
    200: {
      projectId: 200,
      projectName: 'ABC'
    },
    300: {
      projectId: 300,
      projectName: 'DEF'
    }
  }
});

With a structure like this, the answer to your original question would be as easy as a single updateIn:

const newState = state.updateId(['projects', 200],
  project => project.set('projectName', 'NEW_PROJECT_NAME'));
0
On

I had something similar a long time ago... I used flatMap function. Eventually I modified my state structure as maintaining this code was a nightmare.

updateProduct = (id, newName, data) => {
        return {
            departments: Immutable.flatMap(data.departments, (department => {
                return {
                    ...department,
                    employees: Immutable.flatMap(department.employees, (employee => {
                        return {
                            ...employee,
                            projects: Immutable.flatMap(employee.projects, (project => {
                                if (project.projectId == id) {
                                    return {
                                        ...project,
                                        projectName: newName
                                    }
                                }
                                else {
                                    return {...project}
                                }
                            }))
                        }
                    }))
                }
            }))
        }
    }