ZaladiN
ZaladiN

Reputation: 15

How to recursively add object key-value pairs to an array of objects with child arrays?

I'm still learning the ropes of recursion, and I would like to add 'isChecked: false' to every object in this nested tree of arrays.

const nestedMap = (arr) => {
  const result = arr.map(row => {
    if (row.children.length) {
      const children = row.children.map(child => {
        return { ...child, isChecked: false };
      });
      return { ...row, isChecked: false, children };
    } else {
      return { ...row, isChecked: false };
    }
  });
  return result[0].children[0]
}

nestedMap([{title: 'page1', children: [{title: 'page2', children: [{title: 'page4'}]}, {title: 'page3'}]}, {title: 'page5', children: []}])

Here's my result - as you can see I'm successfully updating the first set of children, but not the rest.

{ title: 'page2',
  children: [ { title: 'page4' } ],
  isChecked: false } 

I know I'm supposed to call the function somewhere inside itself, but I'm an idiot. As always, any help would be greatly appreciated. Thank you.

Upvotes: 1

Views: 681

Answers (2)

Scott Sauyet
Scott Sauyet

Reputation: 50797

Another possibility is to write a more generic nestedMap implementation, and then pass it a function which adds your property. Here's how I might write it:

const nestedMap = (fn) => (xs) => 
  xs .map (({children, ...rest}) => fn ({
    ... rest,
    ... (children ? {children: nestedMap (fn) (children)} : {})
  }))

const addProp = (key, val) => (obj) => 
  ({... obj, [key]: val})

const pages = [{title: 'page1', children: [{title: 'page2', children: [{title: 'page4'}]}, {title: 'page3'}]}, {title: 'page5', children: []}]

console .log (
  nestedMap (addProp ('checked', false)) (pages)
)

This version does not mutate our original, but creates a new array full of new objects. (It does not do a full clone; other properties of our nodes may be shared by reference.)

addProp is simple enough: it takes a key and a value and returns a function which takes an object, returning a new object with all its properties and the new key/value.

nestedMap recursively maps a function over a node: {..., children: [more nodes]} structure. The transformation function supplied to it is called first (recursively) on the children of each node and then on the node itself.

Upvotes: 1

Blundering Philosopher
Blundering Philosopher

Reputation: 6805

Your if-statement if (row.children.length) { checks if row.children is an array with length > 0, so here's where you'd want to call nestedMap again.

Try something like this:

const nestedMap = (arr) => {
  const result = arr.map(row => {
    // check if row.children exists AND if its length exists / is greater than 0
    if (row.children && row.children.length) {
      const children = nestedMap(row.children);
      return { ...row, isChecked: false, children };
    } else {
      return { ...row, isChecked: false };
    }
  });
  // Note: You should probably return the entire result here, not result[0].children[0]
  return result;
}

let output = nestedMap([{title: 'page1', children: [{title: 'page2', children: [{title: 'page4'}]}, {title: 'page3'}]}, {title: 'page5', children: []}])

console.log(output)

Notice I made 3 changes:

  1. if (row.children && row.children.length) {
    • I added the check to make sure row.children exists, before checking row.children.length
  2. const children = nestedMap(row.children);
    • This is where we do the recursion :)
  3. return result;
    • This will return the entire result (the original nested array of objects, with isChecked: false in every object).
    • Note: I'm not sure why you ended your function with result[0].children[0], which will only return the original array's first item's first child?

Upvotes: 2

Related Questions