1

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?

3
  • Does this answer your question? find and modify deeply nested object in javascript array Commented May 12, 2020 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(). Commented May 12, 2020 at 15:16
  • A workaround would be to break up the nested state into a component tree where each child manages its own state. Commented May 12, 2020 at 15:18

1 Answer 1

-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));

Sign up to request clarification or add additional context in comments.

5 Comments

This mutates React's state, which you're not supposed to do.
...so so bad dont do this.
Object.assign isn't good. It mutates state. Spread operator is the way to go.
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.
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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.