Search code examples
javascripttypescriptrecursiontreetreeview

Recursively change a tree isDisable boolean


Given a tree:

type DataType = {
  id: string;
  access: 'view' | 'none';
  isDisabled: boolean;
  children: DataType[];
};

const Data: DataType = {
  id: '1',
  access: 'view',
  isDisabled: false,
  children: [
    {
      id: '2',
      access: 'view',
      isDisabled: false,
      children: [
        {
          id: '3',
          access: 'view',
          isDisabled: false,
          children: [],
        },
        {
          id: '4',
          access: 'view',
          isDisabled: false,
          children: [],
        },
      ],
    },
    {
      id: '5',
      access: 'view',
      isDisabled: false,
      children: [],
    },
  ],
};

What I want to do is when a node's access changes, the isDisabled boolean will change based on the following rules:

If access is 'view', the isDisabled will remain false If access is 'none', the isDisabled in the current node will be false, but it's children's isDisabled will be true.

This is what I have now:

export const find = (data, selectedId, access) => {
  if (!selectedId || !access) {
    return data;
  }

  if (data.id === selectedId) {
    data = changeAccess(data, selectedId, access);
    return data;
  }
  data.children.map((child) => find(child, selectedId, access));

  return data; // this is where the problem lies, but not sure how to fix it
};

export const changeAccess = (data, selectedId, access) => ({
  ...data,
  access: access,
  isDisabled: data.id !== selectedId && access !== 'view' ? true : false, // this toggles isDisabled
  children: data.children?.map((child) =>
    changeAccess(child, selectedId, access)
  ),
});

So when I call 'find'

const result = find(Data, '2', 'none');

result should equal the below:

const DataAfter: DataType = {
  id: '1',
  access: 'view',
  isDisabled: false,
  children: [
    {
      id: '2',
      access: 'none',
      isDisabled: false,
      children: [
        {
          id: '3',
          access: 'none',
          isDisabled: true,
          children: [],
        },
        {
          id: '4',
          access: 'none',
          isDisabled: true,
          children: [],
        },
      ],
    },
    {
      id: '5',
      access: 'view',
      isDisabled: false,
      children: [],
    },
  ],
};

Solution

  • The part you highlighted in your code:

      data.children.map((child) => find(child, selectedId, access));
    
      return data; // this is where the problem lies, but not sure how to fix it
    

    ... is not using the array returned by .map(). And so the data is returned in its original state.

    You should return a new object when the mapped children array has one or more new objects:

      const children = data.children.map((child) => find(child, selectedId, access));
      if (data.children.some((child, i) => child !== children[i])) {
        data = {
          ...data,
          children
        };
      }
    
      return data;