26-React File Manager Chapter XXVI: The Node Watcher
Tags: react, typescript, mongez, javascript
Published: 2022-09-20 16:43:24

---
title: 26-React File Manager Chapter XXVI: The Node Watcher
published: true
description: 
series: File Manager React Mongez
tags: react, typescript, mongez, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9pywwf2u56qc6da973mi.png
# Use a ratio of 100:42 for best results.
---

So we've made our kernel to contain a beautiful tree, now we need to make it watch for changes in the tree, so we can update the tree when a new node is added or removed.

## Create Directory

Now let's return to our `createDirectory` action, we should update the kernel tree once the directory is created.

```ts
// createDirectory.ts
import { toastLoading } from "design-system/components/Toast";
import Kernel from "../Kernel";
import fileManagerService from "../services/file-manager-service";

export default function createDirectory(kernel: Kernel) {
  return function create(
    directoryName: string,
    directoryPath: string = kernel.currentDirectoryNode?.path as string,
  ) {
    return new Promise((resolve, reject) => {
      const loader = toastLoading(
        "Creating directory...",
        "We are creating your directory, please wait a moment.",
      );

      fileManagerService
        .createDirectory(directoryName, directoryPath)
        .then(response => {
          loader.success("Success!", "Your directory has been created.");

          👉🏻 kernel.tree.setNode(response.data.node);

          resolve(response.data.node);
        })
        .catch(error => {
          loader.error("Error", error.response.data.error);
          reject(error);
        });
    });
  };
}
```

Now we told the kernel tree to set this node where it belongs, so it will update the tree.

## useNodeWatcher

Now let's create a hook that will watch for changes in the tree, and update the tree when a change is detected.

```ts
// hooks/useNodeWatcher.ts
import { Node } from "app/file-manager/Kernel";
import { useEffect, useState } from "react";
import useKernel from "./useKernel";

export default function useWatchNodeChange(node?: Node) {
  const kernel = useKernel();

  // Store the node internally in a state
  const [internalNode, setNode] = useState<Node | undefined>(node);

  useEffect(() => {
    // watch for node change
    const event = kernel.on("nodeChange", (newNode: Node) => {
      // if the updated node is the same as the one we are watching
      // then update the internal node
      if (newNode.path === internalNode?.path) {
        setNode({ ...newNode });
      }
    });

    return () => event.unsubscribe();
  }, [internalNode, kernel]);

  useEffect(() => {
    setNode(node);
  }, [node]);

  return internalNode;
}
```

Nothing here to explain, we stored the node in a state, and we're watching for changes in the kernel, if the updated node is the same as the one we're watching, then we update the internal node.

## Updating the Nodes List

As we're already listening for directory change event, we need also to watch for the node itself to update the nodes list.

```tsx
// NodesList.tsx
import { Grid } from "@mantine/core";
import {
  useCurrentDirectoryNode,
  useNodeWatcher,
} from "app/file-manager/hooks";
import { DirectoryNode, FileNode } from "./ContentNode";

export default function NodesList() {
  const currentDirectoryNode = useCurrentDirectoryNode();

  const node = useNodeWatcher(currentDirectoryNode);

  return (
    <>
      <Grid>
        {node?.directories?.map(node => (
          <Grid.Col key={node.path} span={2}>
            <DirectoryNode node={node} />
          </Grid.Col>
        ))}
        {node?.files?.map(node => (
          <Grid.Col key={node.path} span={2}>
            <FileNode node={node} />
          </Grid.Col>
        ))}
      </Grid>
    </>
  );
}
```

We removed the memo that collects the files and directories from the node thanks to `prepareNode` method.

Then we added the `useNodeWatcher` hook to watch for changes in the node.

## Updating Sidebar

As the root is also considered as a node, we need to update the sidebar to watch for changes in the root node.

```tsx
// Sidebar.tsx
import {
  Card,
  ScrollArea,
  Skeleton,
  ThemeIcon,
  useMantineTheme,
} from "@mantine/core";
import { IconFolder, IconHome2 } from "@tabler/icons";
import { useKernel, useLoading } from "app/file-manager/hooks";
import useWatchNodeChange from "../../hooks/useNodeWatcher";
import { SidebarWrapper } from "./Sidebar.styles";
import SidebarNode from "./SidebarNode";

export default function Sidebar() {
  const isLoading = useLoading();

  const theme = useMantineTheme();

  // get the kernel
  const kernel = useKernel();

  // watch for the root node for change
  const rootNode = useWatchNodeChange(kernel.rootNode);

  if (isLoading) {
    return (
      <Card shadow={"sm"}>
        <SidebarWrapper>
          <Skeleton height={8} mt={6} radius="xl" />
          <Skeleton height={12} mt={6} width="80%" radius="sm" />
          <Skeleton height={8} mt={6} width="60%" radius="xl" />
          <Skeleton height={8} mt={6} radius="xl" />
          <Skeleton height={12} mt={6} width="80%" radius="sm" />
          <Skeleton height={8} mt={6} width="60%" radius="xl" />
          <Skeleton height={8} mt={6} radius="xl" />
          <Skeleton height={12} mt={6} width="80%" radius="sm" />
          <Skeleton height={8} mt={6} width="60%" radius="xl" />
          <Skeleton height={8} mt={6} radius="xl" />
          <Skeleton height={12} mt={6} width="80%" radius="sm" />
          <Skeleton height={8} mt={6} width="60%" radius="xl" />
        </SidebarWrapper>
      </Card>
    );
  }

  // if no root node yet, return null
  if (!rootNode) return null;

  return (
    <Card shadow="sm">
      <SidebarWrapper>
        <ScrollArea type="auto" style={{ height: "300px" }}>
          <SidebarNode
            node={rootNode}
            icon={
              <ThemeIcon variant="light" color={theme.colors.lime[1]}>
                <IconHome2 size={16} color={theme.colors.lime[9]} />
              </ThemeIcon>
            }
          />
          {rootNode.directories?.map(child => (
            <SidebarNode
              navProps={{
                pl: 25,
              }}
              key={child.path}
              icon={
                <ThemeIcon variant="light" color={theme.colors.blue[1]}>
                  <IconFolder size={16} color={theme.colors.blue[5]} />
                </ThemeIcon>
              }
              node={child}
            />
          ))}
        </ScrollArea>
      </SidebarWrapper>
    </Card>
  );
}
```

We added the `useWatchNodeChange` hook to watch for changes in the root node.

Let's move the sidebar skeleton to a separate component.

```tsx
// SidebarSkeleton.tsx
import { Card, Skeleton } from "@mantine/core";

export default function SidebarSkeleton() {
  return (
    <Card shadow={"sm"}>
      <Skeleton height={8} mt={6} radius="xl" />
      <Skeleton height={12} mt={6} width="80%" radius="sm" />
      <Skeleton height={8} mt={6} width="60%" radius="xl" />
      <Skeleton height={8} mt={6} radius="xl" />
      <Skeleton height={12} mt={6} width="80%" radius="sm" />
      <Skeleton height={8} mt={6} width="60%" radius="xl" />
      <Skeleton height={8} mt={6} radius="xl" />
      <Skeleton height={12} mt={6} width="80%" radius="sm" />
      <Skeleton height={8} mt={6} width="60%" radius="xl" />
      <Skeleton height={8} mt={6} radius="xl" />
      <Skeleton height={12} mt={6} width="80%" radius="sm" />
      <Skeleton height={8} mt={6} width="60%" radius="xl" />
    </Card>
  );
}
```

Now our sidebar component looks like this.

```tsx
// Sidebar.tsx
import { Card, ScrollArea, ThemeIcon, useMantineTheme } from "@mantine/core";
import { IconFolder, IconHome2 } from "@tabler/icons";
import { useKernel, useLoading } from "app/file-manager/hooks";
import useWatchNodeChange from "../../hooks/useNodeWatcher";
import { SidebarWrapper } from "./Sidebar.styles";
import SidebarNode from "./SidebarNode";
import SidebarSkeleton from "./SidebarSkeleton";

export default function Sidebar() {
  const isLoading = useLoading();

  const theme = useMantineTheme();

  // get the kernel
  const kernel = useKernel();

  // watch for the root node for change
  const rootNode = useWatchNodeChange(kernel.rootNode);

  if (isLoading) return <SidebarSkeleton />;

  // if no root node yet, return null
  if (!rootNode) return null;

  return (
    <Card shadow="sm">
      <SidebarWrapper>
        <ScrollArea type="auto" style={{ height: "300px" }}>
          <SidebarNode
            node={rootNode}
            icon={
              <ThemeIcon variant="light" color={theme.colors.lime[1]}>
                <IconHome2 size={16} color={theme.colors.lime[9]} />
              </ThemeIcon>
            }
          />
          {rootNode.directories?.map(child => (
            <SidebarNode
              navProps={{
                pl: 25,
              }}
              key={child.path}
              icon={
                <ThemeIcon variant="light" color={theme.colors.blue[1]}>
                  <IconFolder size={16} color={theme.colors.blue[5]} />
                </ThemeIcon>
              }
              node={child}
            />
          ))}
        </ScrollArea>
      </SidebarWrapper>
    </Card>
  );
}
```

## Next Chapter

In the next chapter we'll start make the node selection algorithm.

## Article Repository

You can see chapter files in [Github Repository](https://github.com/hassanzohdy/file-manager-react/tree/26-the-node-watcher)

> Don't forget the `main` branch has the latest updated code.

## Tell me where you are now

If you're following up with me this series, tell me where are you now and what you're struggling with, i'll try to help you as much as i can.

Salam.