Let's create a React File Manager Chapter VI: Fetching Directories And Files
Tags: react, typescript, mongez
Published: 2022-09-13 14:34:47

---
title: Let's create a React File Manager Chapter VI: Fetching Directories And Files 
published: true
description: 
series: File Manager React Mongez
tags: react, typescript, mongez
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/314szylzr6lwrc0bizyb.png
# Use a ratio of 100:42 for best results.
---

Now let's start working with some logic, let's first define our workflow.

## Workflow

Once the file manager is opened, we'll load the `root` path then list its directories on the sidebar, and its content (Files And Directories) on the right side.

So we need the following states:

- `Loading` State to determine if the file manager is being loading directories.
- `currentDirectoryNode`: State that contains the current loaded directory node.

That's it for now, let's see it in action.

```tsx
// FileManager.tsx

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

  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: "/",
};
```

I added `rootPath` in the default props object, and created two states as mentioned earlier.

Now let's make a new instance of the file manager and store it in a `ref`

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

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

  const fileManagerRef = useRef(new BaseFileManager());

  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: "/",
};
```

We renamed it to `BaseFileManager` so we don't get confused between it and the component itself.

We actually may enhance the ref by directly destructing the `current` key

```diff
- const fileManagerRef = useRef(new BaseFileManager());
+ const { current: fileManager } = useRef(new BaseFileManager());
```

Next let's create the `useEffect` hook that will load the root path.

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

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

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

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

    setIsLoading(true);

    fileManager.load(rootPath).then(() => {
      setIsLoading(false);
      setCurrentDirectoryPath(rootPath);
    });
  }, [rootPath, fileManager]);

  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: "/",
};
```

Now we added the `rootPath` to the dependency array, so the `useEffect` will be called once the `rootPath` is changed, also we called `load` as it will load the given path as current directory.

Before we jump to the `load` method, let's add another condition that if the file manager is not open then ignore the load.

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

    setIsLoading(true);

    fileManager.load(rootPath).then(directoryNode => {
      setIsLoading(false);
      currentDirectoryNode(directoryNode);
    });
  }, [rootPath, fileManager, open]);
```

## Creating load method

The `load` method will load the given path and return a promise that will be resolved once the path is loaded, also it will return a `Node` with the loaded directory as well.

Also we need to define the root path in the file manager, so we'll create a `setRootPath` method.

```tsx

// file-manager/utils/FileManager.ts
import { Node } from "../types/FileManager.types";

export default class FileManager {
  /**
   * Root path
   */
  protected rootPath = "/";

  /**
   * Current directory path
   */
  protected currentDirectoryPath = "/";

  /**
   * Current directory node
   */
  protected currentDirectoryNode?: Node;

  /**
   * Set root path
   */
  public setRootPath(rootPath: string): FileManager {
    this.rootPath = rootPath;
    return this;
  }
}
```

Heading to `FileManagerService` let's create a new instance from it and export it so we can use it directly.

At the end of the file, we'll export the instance.

```tsx
// file-manager-service.ts
const fileManagerService = new FileManagerService();

export default fileManagerService;
```

Now let's define the `load` method in the `FileManager`

```tsx
// FileManager.ts
import fileManagerService from "../services/file-manager-service";
import { Node } from "../types/FileManager.types";

export default class FileManager {
  /**
   * Root path
   */
  protected rootPath = "/";

  /**
   * Current directory path
   */
  protected currentDirectoryPath = "/";

  /**
   * Current directory node
   */
  protected currentDirectoryNode?: Node;

  /**
   * Set root path
   */
  public setRootPath(rootPath: string): FileManager {
    this.rootPath = rootPath;
    return this;
  }

  /**
   * Load the given path
   */
  public load(path: string): Promise<Node> {
    return new Promise((resolve, reject) => {
      fileManagerService
        .list(path)
        .then(response => {
          this.currentDirectoryPath = path;
          this.currentDirectoryNode = response.data.node;
          resolve(this.currentDirectoryNode as Node);
        })
        .catch(reject);
    });
  }
}
```

We used the `list` method from the `FileManagerService` to get the directory node, then we stored it in the `currentDirectoryNode` and returned it.

But we need to make a small modification in the `list` method, we need to return a single node from the backend that contains all listed children inside it directly, so the backend handle it.

```diff
import FileManagerServiceInterface from "../types/FileManagerServiceInterface";
import { 
+    newNode, 
-    listNodes
     } from "../utils/data";

export class FileManagerService implements FileManagerServiceInterface {
  /**
   * {@inheritDoc}
   */
  public list(directoryPath: string): Promise<any> {
    return new Promise(resolve => {
      resolve({
        data: {
-          node: listNodes(),
+          node: newNode(),
        },
      });
    });
  }
}

const fileManagerService = new FileManagerService();

export default fileManagerService;
```

Going back to the `load` method, we also defined the current directory node which will be resolved for the load promise.

Now let's head to our `FileManager.tsx` component and console.log `currentDirectoryNode` to see what will be returned

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

  console.log(currentDirectoryNode);
```

As it is being randomly generated let's make another generated node for directories to make sure we always have directory.

```ts
// utils/data.ts

export function newNode(): Node {
  const isDirectory = faker.datatype.boolean();
  const node: Node = {
    name: isDirectory ? faker.system.directoryPath() : faker.system.fileName(),
    path: faker.system.filePath(),
    size: faker.datatype.number({ min: 1, max: 100000 }),
    isDirectory,
  };

  if (node.isDirectory) {
    node.children = listNodes(1, 3);
  }

  return node;
}

export function newDirectoryNode() {
  const node = newNode();
  node.children = listNodes(faker.datatype.number({ min: 3, max: 4 }), 5);
  node.name = faker.system.directoryPath();
  node.isDirectory = true;
  return node;
}
```

I also made small modification on `listNodes` to accept min and max values to make sure we always have a directory with at least three children.

Now let's update our service class to call the `newDirectoryNode` instead of `newNode`

```diff
- import { newNode } from "../utils/data";
+ import { newDirectoryNode } from "../utils/data";

    return new Promise(resolve => {
      resolve({
        data: {
-          node: newNode(),
+          node: newDirectoryNode(),
        },
      });
    });
```

We'll pause here and continue in the next article, if you get confused read the final code from the repository below then read the article again.

Next article will be about listing directories in the sidebar.

## Article Repository

You can see chapter files in [Github Repository](https://github.com/hassanzohdy/file-manager-react/tree/6-fetching-directories-files)

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

Salam.