2

I've got a deeply nested array that looks like this:

const elements = [
  {
    type: "section",
    id: "basic-section",
    title: "Basic information",
    children: [
      {
        type: "select",
        label: "Entity type",
        id: "entity-type",
        options: [
          { value: "person", label: "Person" },
          { value: "company", label: "Company" },
          { value: "organisation", label: "Organisation" },
        ],
      },
      {
        type: "group",
        conditions: [
          { type: "isEqual", variable: "entity-type", value: ["person"] },
        ],
        children: [
          { type: "text", label: "First name", id: "entity.firstName" },
          { type: "text", label: "Last name", id: "entity.lastName" },
          { type: "number", label: "Age", id: "entity.age" },
          {
            type: "select",
            label: "Gender",
            id: "entity.gender",
            defaultValue: "female",
            options: [
              { value: "male", label: "Male" },
              { value: "female", label: "Female" },
            ],
          },
        ],
      },
      {
        type: "group",
        conditions: [
          {
            type: "isEqual",
            variable: "entity-type",
            value: ["company", "organisation"],
          },
        ],
        children: [
          { type: "text", label: "Name", id: "entity.name" },
          { type: "text", label: "Address", id: "entity.address" },
        ],
      },
    ],
  },
];

I'm trying to add and update a property based on a given key and value.

Example 1: Add an option to the options list of entity-type

Example 2: Update the defaultValue of entity.gender to male

My current steps to accomplish this actions are:

1) Find the element based on the id key and id value

const element = findObject(elements, 'id', 'entity-type');

function findObject(object, key, value) {
    if(object.hasOwnProperty(key) && object[key] === value) {
        return object;
    }

    for(let i = 0; i < Object.keys(object).length; i++){
        if(typeof object[Object.keys(object)[i]] == "object") {
            const o = findObject(object[Object.keys(object)[i]], key, value);
            if(o !== null) return o;
        }
    }

    return null;
}

2) Create new option

const newOption = { value: 'government', label: 'Government' };

3) Add the new option to the found element

const updatedElement = Object.assign({}, element, { options: [...element.options, newOption] });

4) Replace the old element with the updatedElement

const newElementsList = // Stuck

5) Update the state with the updatedElementsList

setElementsList(newElementsList);

I don't see how I can replace the original element with the updated one based on the key and value.

Can someone help me out?

xdeepakv
  • 7,835
  • 2
  • 22
  • 32
Thore
  • 1,918
  • 2
  • 25
  • 50
  • Does this answer your question? [find and modify deeply nested object in javascript array](https://stackoverflow.com/questions/25569255/find-and-modify-deeply-nested-object-in-javascript-array) – Heretic Monkey May 12 '20 at 15:12
  • 1
    The problem is that `findObject` doesn't tell you anything about where in the tree it is. You need to store the path in an array, like `["children", 1, "children", 3]`, then walk it backwards and create each updated object/array in turn. Then finally setState(). –  May 12 '20 at 15:16
  • A workaround would be to break up the nested state into a component tree where each child manages its own state. –  May 12 '20 at 15:18

1 Answers1

-1

This is not recommended, but you can keep track of parent. Once you find the element, update the parent data with update value. But you loose immutability.

A better approach would be found and update the same time.

const elements = [{"type":"section","id":"basic-section","title":"Basic information","children":[{"type":"select","label":"Entity type","id":"entity-type","options":[{"value":"person","label":"Person"},{"value":"company","label":"Company"},{"value":"organisation","label":"Organisation"}]},{"type":"group","conditions":[{"type":"isEqual","variable":"entity-type","value":["person"]}],"children":[{"type":"text","label":"First name","id":"entity.firstName"},{"type":"text","label":"Last name","id":"entity.lastName"},{"type":"number","label":"Age","id":"entity.age"},{"type":"select","label":"Gender","id":"entity.gender","defaultValue":"female","options":[{"value":"male","label":"Male"},{"value":"female","label":"Female"}]}]},{"type":"group","conditions":[{"type":"isEqual","variable":"entity-type","value":["company","organisation"]}],"children":[{"type":"text","label":"Name","id":"entity.name"},{"type":"text","label":"Address","id":"entity.address"}]}]}];
// console.log("%j", elements);


function findObject(element, key, value, { parent = null, index = -1 }) {
  if (element.hasOwnProperty(key) && element[key] === value) {
    return { element: element, parent, index };
  }
  let keys = Object.keys(element);
  for (let i = 0; i < keys.length; i++) {
    if (typeof element[keys[i]] == "object") {
      const o = findObject(element[Object.keys(element)[i]], key, value, {
        parent: element,
        index: i,
      });
      if (o !== null) return o;
    }
  }
  return { element: null };
}
const { element, parent, index } = findObject(
  elements,
  "id",
  "entity-type",
  {}
);
const newOption = { value: "government", label: "Government" };
const updatedElement = Object.assign({}, element, {
  options: [...element.options, newOption],
});
if (parent && index !== -1) parent[index] = updatedElement;
console.log(JSON.stringify(elements, null, 4));
xdeepakv
  • 7,835
  • 2
  • 22
  • 32
  • 1
    This mutates React's state, which you're not supposed to do. –  May 12 '20 at 15:56
  • 1
    ...so so bad dont do this. – AndrewMcLagan Oct 08 '20 at 06:33
  • `Object.assign` isn't good. It mutates state. Spread operator is the way to go. – knoxgon Feb 18 '21 at 10:40
  • Object.assign is use to polyfill spread operator. When use are using blank object while assign. It is like creating new object all the time same as spread operator. – xdeepakv Feb 24 '21 at 09:55
  • And other folks, i have written it is not recommended even in first line. If u dont appreciate the solutions. Dont downvote because it hack. May be someone needed that hack – xdeepakv Feb 24 '21 at 09:57