0
const arr = [
  {
    id: "8bfc-2b82-68ad-fb78",
    name: "inventory",
    type: "directory",
    path: "inventory/",
    children: [
      {
        id: "bccf-3788-6ec9-ba33",
        name: "inventory.yaml",
        type: "file",
        path: "inventory/inventory.yaml",
      },
    ],
  },
  {
    id: "4a28-4d63-6c30-4569",
    name: "model",
    type: "directory",
    path: "model/",
    children: [
      {
        id: "21d1-f4d2-e1c5-34b0",
        name: "model.yaml",
        type: "file",
        path: "model/model.yaml",
      },
    ],
  },
];








  function _worktreeItemDelete({ detail }) {
    const currentId = detail.item.id;
    const filtered = this.tree.filter(entry => entry.id !== currentId);
    const updTree = filtered.map(entry => {
      if (!entry.children) return entry;
      return {...entry, children: this._worktreeItemDelete(entry.children, currentId)};
    });
    console.log(updTree);
    this.set("tree", updTree);
  }

This is what I am doing . currentId i am catching as an event property value. The problem that i am having right now is:

Uncaught RangeError: Maximum call stack size exceeded

If I understood it right, the recursion is not stopping, looking for a way to handle this issue. Appreciate your help.

Sarvesh Mahajan
  • 914
  • 7
  • 16
Ian
  • 77
  • 1
  • 9

3 Answers3

2

function removeById(arr, id) {
  return arr.filter(item => {
    arr.children = arr.children && removeById(arr.children);
    return item.id !== id;
  });
}

you can use this helper function in your code

const filtered = removeById(this.tree, currentId);
Ziarno
  • 7,366
  • 5
  • 34
  • 40
2

Here is an iterative solution using object-scan. Using a library for your use case might be overkill, however it might make it a lot easier to maintain the function if that's needed.

E.g. adjusting it to allow for deletion of non-unique ids would just require removing the abort flag.

// const objectScan = require('object-scan');

const myArr = [{ id: '8bfc-2b82-68ad-fb78', name: 'inventory', type: 'directory', path: 'inventory/', children: [{ id: 'bccf-3788-6ec9-ba33', name: 'inventory.yaml', type: 'file', path: 'inventory/inventory.yaml' }] }, { id: '4a28-4d63-6c30-4569', name: 'model', type: 'directory', path: 'model/', children: [{ id: '21d1-f4d2-e1c5-34b0', name: 'model.yaml', type: 'file', path: 'model/model.yaml' }] }];

const removeById = (arr, id) => objectScan(['**(^children$).id'], {
  useArraySelector: false,
  filterFn: ({ value, gparent, gproperty }) => {
    if (value === id) {
      gparent.splice(gproperty, 1);
      return true;
    }
    return false;
  },
  abort: true,
  rtn: 'bool'
})(arr);

console.log(removeById(myArr, '21d1-f4d2-e1c5-34b0'));
// => true

console.log(myArr);
/* =>
[
  {
    id: '8bfc-2b82-68ad-fb78',
    name: 'inventory',
    type: 'directory',
    path: 'inventory/',
    children: [
      {
        id: 'bccf-3788-6ec9-ba33',
        name: 'inventory.yaml',
        type: 'file',
        path: 'inventory/inventory.yaml'
      }
    ]
  },
  {
    id: '4a28-4d63-6c30-4569',
    name: 'model',
    type: 'directory',
    path: 'model/',
    children: []
  }
]
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@15.0.0"></script>

Disclaimer: I'm the author of object-scan

vincent
  • 1,953
  • 3
  • 18
  • 24
  • 1
    I'm curious about "iterative solution" here. I haven't dug into the code in object-scan, but does it not use recursion for such problems? Of course the user is not writing a directly recursive function, but I don't think I've ever solved this sort of problem without some sort of recursion. Of course it can be done, but I find recursion much simpler. – Scott Sauyet Apr 26 '21 at 11:46
  • 1
    Great question! Object-scan is completely recursion free under the hood (even the compile stage is). Turned out recursion was much slower for the core loop, had problems with stack overflows for big objects and used more memory. Complexity tradeoff, but as you said, it's all well hidden. – vincent Apr 26 '21 at 13:51
  • Thanks. That's good to know. Some day, I will look carefully at the code... – Scott Sauyet Apr 26 '21 at 18:52
  • 1
    Haha, good luck with that. I refactored it a lot last year, so it's not as bad as it was ;) – vincent Apr 26 '21 at 20:33
1

Fixing the function

You're close. The problem is that you are passing an object to the initial delete call, extracting the nested property id to use properly, but then when you make the recursive call, you're using only that id, not the whole object.

The quickest fix would probably to keep a reference to that object, and not destructure it in the parameter. I don't have enough understanding of the context to name it appropriately, so I'll just call it foo. Then we can pass foo to the recursive call, and it will just work.

With that fix, a variant of your function will work properly:

function _worktreeItemDelete(tree, foo) {
  const currentId = foo.detail.item.id;
  const filtered = tree.filter(entry => entry.id !== currentId);
  const updTree = filtered.map(entry => {
    if (!entry.children) return entry;
    return {...entry, children: _worktreeItemDelete(entry.children, foo)};
  });
  return updTree
}

const arr = [{id: "8bfc-2b82-68ad-fb78", name: "inventory", type: "directory", path: "inventory/", children: [{id: "bccf-3788-6ec9-ba33", name: "inventory.yaml", type: "file", path: "inventory/inventory.yaml", }]}, {id: "4a28-4d63-6c30-4569", name: "model", type: "directory", path: "model/", children: [{id: "21d1-f4d2-e1c5-34b0", name: "model.yaml", type: "file", path: "model/model.yaml"}]}]

console .log (
  _worktreeItemDelete (arr, {detail: {item: {id: 'bccf-3788-6ec9-ba33'}}})
)
.as-console-wrapper {max-height: 100% !important; top: 0}

Note that I changed this to a pure function rather than a method, hence skipping this.set("tree", updTree), adding a tree parameter, and removing the this references. They should be easy to put back.

However, the above does not explain the recursion depth error you described. I would expect something more like

Cannot read property 'item' of undefined

So there may be something additional happening.

An alternate approach

I would write this code differently. I would use a generic version of a tree-filtering function and pass it a predicate for node-selection.

My version might look like this:

const deepFilter = (pred) => (xs) =>
  xs .flatMap (({children = [], ...rest}) => 
    pred (rest)
      ? [{... rest, children: deepFilter (pred) (children)}]
      : []
  )

const removeById = ({detail: {item: {id: target}}}) => 
  deepFilter (({id}) => id !== target)

const arr = [{id: "8bfc-2b82-68ad-fb78", name: "inventory", type: "directory", path: "inventory/", children: [{id: "bccf-3788-6ec9-ba33", name: "inventory.yaml", type: "file", path: "inventory/inventory.yaml", }]}, {id: "4a28-4d63-6c30-4569", name: "model", type: "directory", path: "model/", children: [{id: "21d1-f4d2-e1c5-34b0", name: "model.yaml", type: "file", path: "model/model.yaml"}]}]

console .log (
  removeById ({detail: {item: {id: 'bccf-3788-6ec9-ba33'}}}) (arr)
)
.as-console-wrapper {max-height: 100% !important; top: 0}

Note that deepFilter is generic. It works for any tree where the descendent nodes are in the property children. (See another answer for a more generic version that lets you configure this property.)

So the custom code here is just removeById. To my mind, that's a nice win.

If it is important, we can -- at the price of a little additional complexity -- choose to only include the children node if the data is not empty. If you want that and need help with it, just add a comment.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • 1
    Wow, thank so much for your help! Just need to figure it out how `deepFilter` works - what is pred and xs? – Ian Apr 23 '21 at 12:31
  • 1
    `pred` is my usual shorthand for a predicate function, i.e. a function that returns a boolean. `xs` is just an arbitrary array, presumably with items that could have `children` properties that we want to recurse into. – Scott Sauyet Apr 23 '21 at 12:39