brimdata / react-arborist

The complete tree view component for React
MIT License
2.88k stars 123 forks source link

Lazy loading pattern #135

Open waliurjs opened 1 year ago

waliurjs commented 1 year ago

Hi! This is related to #127 Lazy loading children when parent is opened.

Can you guys please provide a pattern/pseudo code or some advice on how to implement it?

What I can gather from the docs is that I think I need to have a controlled component like this. But I have more questions.

Some concerns that I need advice on:

Desperately need some advice or some pseudo codes.

Thank you!

(Found your HackerNews comment)

jameskerr commented 1 year ago

Yes, this is a good idea. I will create a demo with this as I have time.

CHE1RON commented 1 year ago

This would be super helpful, thanks @jameskerr!

holy-dev commented 11 months ago

Any update on this?

holy-dev commented 11 months ago

@jameskerr brother, this is the last thing I need help with promise. Could you please guide me?

jameskerr commented 11 months ago

Here's a PR that was just submitted shows an example of manually creating all the handlers for the Tree.

https://github.com/brimdata/react-arborist/pull/172/files

When a node is clicked, in the click handler, you'll make your api call, update the data, and the tree will re-render.

Good luck!

holy-dev commented 11 months ago

@jameskerr the PR makes sense. I now know why my code wasn't working before. It'll be much easier to implement custom logics.

Philipinho commented 9 months ago

I have been able to achieve lazy loading.

I created a custom hook from #172. I replaced const [data, setData] = useState<T[]>([]) in the hook with jotai atom to be able to read and update the tree data globally.

export const treeDataAtom = atom<any>([]);
const [treeData, setTreeData] = useAtom(treeDataAtom);

In my Node component, the tree update is adapted from Ant design dynamic children .

When the node is clicked, the tree data is updated and persisted to the atom. You can also load the children from an api.

function Node({ node, style, dragHandle }: NodeRendererProps<any>) {
  const [treeData, setTreeData] = useAtom(treeDataAtom);

  function updateTreeData(list, id, children) {
    return list.map((nodeItem) => {
      if (nodeItem.id === id) {
        return { ...nodeItem, children };
      }
      if (nodeItem.children) {
        return { ...nodeItem, children: updateTreeData(nodeItem.children, id, children) };
      }
      return nodeItem;
    });
  }
  function loadChildren({ id, children }) {
    return new Promise(resolve => {
      if (children && children.length > 0) {
        resolve();
        return;
      }

      setTimeout(() => {
        setData(origin =>
          updateTreeData(data, id, [
            {
               id: `${id}-0`,
              name: 'Child Node',
              children: [],
            },
            {
              id: `${id}-1`,
              name: 'Child Good',
              children: [],
            },
          ]),
        );
        resolve();
      }, 1000);
    });
  }

  return (
    <>
      <div
        style={style}
        className={clsx(styles.node, node.state)}
        ref={dragHandle}
        onClick={() => loadChildren(node)}
      >
        <PageArrow node={node} />

        <IconFileDescription size="18px" style={{ marginRight: '4px' }} />

        <span className={styles.text}>
          { node.data.name }
        </span>
      </div>
    </>
  );
}

I am not sure if I am doing it the right way but so far it works.

Working Demo:

https://github.com/brimdata/react-arborist/assets/16838612/c8d36b88-41c6-480f-a56c-033945ada7ec

Related to #127

CHE1RON commented 5 months ago

@Philipinho Would you be willing to share your code? 🥇 I'm working on an implementation right now, and would love to be able to share & compare 😊

Cheers!

Philipinho commented 5 months ago

@CHE1RON It was more of a POC incase I would need it in my application (not yet needed). Unless I would spend some time to create a working demo. Lets see.

stefantrinh1 commented 5 months ago

for those that don't want to do a time out. There is another way as the timeout isn't 100% reliable.

I tried to use a flag in the Node Component with useState, but it would reset the state when the tree re-rendered.

i first tried this but this caused problems with it opening every time the node re rendered and reset the local State Inside the TreeNode Component back to initial.

  useEffect(() => {
    if (children && children.length > 0) {
      node.open()
    }
  }, [children, children.length])

So I reached for the tree ref. and stored any nodes where I had finished lazy loading.


function Node({ node, style, dragHandle, setTree, tree }: any) {
...
  useEffect(() => {
    if (!tree?.store?.idsLoaded?.includes(_id) && node.isInternal) {
      node.open()
      tree.store.idsLoaded = [...(tree?.store?.idsLoaded ?? []), _id]
    }
  }, [node.isInternal, hasLoaded, tree])
 ...
 }

hope this helps anybody trying this

https://github.com/brimdata/react-arborist/assets/32978225/7ba8246f-7f44-463b-9f89-271ecda9aadd

Philipinho commented 2 months ago

@CHE1RON not sure if this is still relevant to you.

This is the live implementation in my software. https://github.com/docmost/docmost/blob/df9110268c9059dd568afac8a74868e6a4815e30/apps/client/src/features/page/tree/components/space-tree.tsx#L132

Hook for mutating tree and syncing with backend: https://github.com/docmost/docmost/blob/df9110268c9059dd568afac8a74868e6a4815e30/apps/client/src/features/page/tree/hooks/use-tree-mutation.ts#L24

I am using Jotai to manage the tree data across components.

Happy to clear any doubts.

https://github.com/brimdata/react-arborist/assets/16838612/b11ca64b-4daa-4b75-bae4-0938bd778b80