Let's create a React File Manager Chapter VII: Listing Sidebar

hassanzohdy

Hasan Zohdy

Posted on September 13, 2022

Let's create a React File Manager Chapter VII: Listing Sidebar

Now we can see in the previous article that we loaded the root directory contents, now let's display them in the sidebar.

But before we do this, let's enhance out directory loading in the file manager as we later will need a function to load the selected directory, so let's create load function in the FileManager component.

// FileManager.tsx
...
  // load the given directory path
  const load = (path: string) => {
    setIsLoading(true);
    fileManager
      .load(path)
      .then(node => {
        setCurrentDirectoryNode(node);
      })
      .finally(() => {
        setIsLoading(false);
      });
  };

  // load root directory
  useEffect(() => {
    if (!rootPath || !open) return;

    setIsLoading(true);

    fileManager.load(rootPath).then(directoryNode => {
      setIsLoading(false);
      setCurrentDirectoryNode(directoryNode);
    });
  }, [rootPath, fileManager, open]);
Enter fullscreen mode Exit fullscreen mode

We create the load function and now we need to call it in the useEffect hook to load the root directory.

// FileManager.tsx

  // load root directory
  useEffect(() => {
    if (!rootPath || !open) return;

    load(rootPath);
  }, [rootPath, fileManager, open]);
Enter fullscreen mode Exit fullscreen mode

Now the eslint will complain about the load function not being in the dependencies array, so let's add it but we need to wrap it in a useCallback to prevent reloading the effect multiple times.

// FileManager.tsx

  // load the given directory path
  const load = useCallback(
    (path: string) => {
      setIsLoading(true);
      fileManager
        .load(path)
        .then(node => {
          setCurrentDirectoryNode(node);
        })
        .finally(() => {
          setIsLoading(false);
        });
    },
    [fileManager],
  );

  // load root directory
  useEffect(() => {
    if (!rootPath || !open) return;

    load(rootPath);
  }, [rootPath, fileManager, open, load]);
Enter fullscreen mode Exit fullscreen mode

One more thing to do is we need to store the rootDirectoryNode in a state so it will be available to the Sidebar component.

// FileManager.tsx
...
  const [currentDirectoryNode, setCurrentDirectoryNode] = useState<Node>();
  const [rootDirectoryNode, setRootDirectoryNode] = useState<Node>();
...
  // load the given directory path
  const load = useCallback(
    (path: string, isRoot = false) => {
      setIsLoading(true);
      fileManager
        .load(path)
        .then(node => {
          setCurrentDirectoryNode(node);
          if (isRoot) {
            setRootDirectoryNode(node);
          }
        })
        .finally(() => {
          setIsLoading(false);
        });
    },
    [fileManager],
  );

  // load root directory
  useEffect(() => {
    if (!rootPath || !open) return;

    load(rootPath, true);
  }, [rootPath, fileManager, open, load]);

Enter fullscreen mode Exit fullscreen mode

Now our FileManager will look like:

// FileManager.tsx
import { Grid, Modal } from "@mantine/core";
import BaseFileManager from "app/file-manager/utils/FileManager";
import { useCallback, useEffect, useRef, useState } from "react";
import { Node } from "../../types/FileManager.types";
import Content from "./Content";
import { BodyWrapper } from "./FileManager.styles";
import { FileManagerProps } from "./FileManager.types";
import Sidebar from "./Sidebar";
import Toolbar from "./Toolbar";

export default function FileManager({
  open,
  onClose,
  rootPath,
}: FileManagerProps) {
  const [isLoading, setIsLoading] = useState(true);
  const [currentDirectoryNode, setCurrentDirectoryNode] = useState<Node>();
  const [rootDirectoryNode, setRootDirectoryNode] = useState<Node>();

  const { current: fileManager } = useRef(new BaseFileManager());

  // load the given directory path
  const load = useCallback(
    (path: string, isRoot = false) => {
      setIsLoading(true);

      fileManager.load(path).then(node => {
        setCurrentDirectoryNode(node);

        setIsLoading(false);
        if (isRoot) {
          setRootDirectoryNode(node);
        }
      });
    },
    [fileManager],
  );

  // load root directory
  useEffect(() => {
    if (!rootPath || !open) return;

    load(rootPath, true);
  }, [rootPath, fileManager, open, load]);

  return (
    <>
      <Modal size="xl" opened={open} onClose={onClose}>
        <Toolbar />
        <BodyWrapper>
          <Grid>
            <Grid.Col span={3}>
              <Sidebar />
            </Grid.Col>
            <Grid.Col span={9}>
              <Content />
            </Grid.Col>
          </Grid>
        </BodyWrapper>
      </Modal>
    </>
  );
}

FileManager.defaultProps = {
  rootPath: "/",
};
Enter fullscreen mode Exit fullscreen mode

Sidebar

Now let's update our Sidebar component as we need to display the root directory contents, we need to pass to to it the root node.

// Sidebar.tsx
import { Card } from "@mantine/core";
import { Node } from "../../../types/FileManager.types";

export type SidebarProps = {
  rootDirectory?: Node;
};

export default function Sidebar({ rootDirectory }: SidebarProps) {
  return (
    <>
      <Card shadow="sm">
        <div>Sidebar</div>
      </Card>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now let's loop over the root directory to display its children content, but there is a catch here, we need to display the children of the root directory only if it is a directory.

// Sidebar.tsx
import { Card } from "@mantine/core";
import { useMemo } from "react";
import { Node } from "../../../types/FileManager.types";

export type SidebarProps = {
  rootDirectory?: Node;
};

export default function Sidebar({ rootDirectory }: SidebarProps) {
  const rootChildren = useMemo(() => {
    return rootDirectory?.children?.filter(child => child.isDirectory);
  }, [rootDirectory]);

  if (!rootDirectory) return null;

  return (
    <>
      <Card shadow="sm">
        <div>Sidebar</div>
      </Card>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

We create a memo to store root directory children with simple filter, get only children nodes that has isDirectory property set to true.

Now let's go back to our FileManager component and pass the rootDirectoryNode to the Sidebar component.

// FileManager.tsx

<Grid.Col span={3}>
-    <Sidebar />
+    <Sidebar rootDirectory={rootDirectoryNode} />
</Grid.Col>
Enter fullscreen mode Exit fullscreen mode

Now you should see something like this:

Sidebar

Display Sidebar Nodes

Let's enhance our code a little bit and create a sidebar node for each node so we can get more controls and code get less complex, we'll call it SidebarNode.tsx, we'll create it in the same directory of Sidebar.

// SidebarNode.tsx
import { Node } from "../../../types/FileManager.types";

export type SidebarNodeProps = {
  node: Node;
};

export default function SidebarNode({ node }: SidebarNodeProps) {
  return (
    <>
      <div>{node.name}</div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now let's update our Sidebar component to display the SidebarNode for each node.

// Sidebar.tsx
import { Card } from "@mantine/core";
import { useMemo } from "react";
import { Node } from "../../../types/FileManager.types";
import SidebarNode from "./SidebarNode";

export type SidebarProps = {
  rootDirectory?: Node;
};

export default function Sidebar({ rootDirectory }: SidebarProps) {
  const rootChildren = useMemo(() => {
    return rootDirectory?.children?.filter(child => child.isDirectory);
  }, [rootDirectory]);

  if (!rootDirectory) return null;

  return (
    <>
      <Card shadow="sm">
        {rootChildren?.map(child => (
          <SidebarNode key={child.path} node={child} />
        ))}
      </Card>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now it looks like this:

File Manager

In the next article, we'll make more improvements in our sidebar like adding icons and also add the home root as well to be displayed at the top of the sidebar.

Article Repository

You can see chapter files in Github Repository

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

Salam.

💖 💪 🙅 🚩
hassanzohdy
Hasan Zohdy

Posted on September 13, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related