Let's create a File Manager From Scratch With React And Typescript Chapter III: Fetching Root Directory
Tags: react, typescript, mongez
Published: 2022-09-11 11:48:40

---
title: Let's create a File Manager From Scratch With React And Typescript Chapter III: Fetching Root Directory 
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 our basic setup is ready, let's make a simple mockup api for our file manager.

## File Manager Service

Let's create `file-manager-service.ts` under `file-manager/services` directory then let's establish it.

```ts
// file-manager-service.ts
export class FileManagerService implements FileManagerServiceInterface {

}
```

We just created an empty class that implements `FileManagerServiceInterface` interface which is clearly not defined yet, so let's define it.

## FileManagerServiceInterface Interface

Let's think a little bit here, what possibly requests will be used in our file manager, let's list them all, or at least the basic requests.

- List Directory contents
- Upload Files
- Rename Directory/File
- Delete Directory/File
- Create Directory
- Move Directory/File
- Copy Directory/File

So we've like 6-7 requests to do, let's create their interface.

Head to `file-manager/types` and create `FileManagerServiceInterface.ts` file

```ts
// file-manager/types/FileManagerServiceInterface.ts
export default interface FileManagerServiceInterface {
  /**
   * List directory contents for the given path
   */
  list(directoryPath: string): Promise<any>;

  /**
   * Create new directory
   *
   * First parameter will be the directory name,
   * second parameter will be the directory path that will be created into.
   */
  createDirectory(directoryName: string, saveTo: string): Promise<any>;

  /**
   * Delete directories/files
   */
  delete(paths: string[]): Promise<any>;

  /**
   * Rename directory | file
   *
   * The first parameter will be the old path,
   * the second parameter will be the new path.
   */
  rename(path: string, newPath: string): Promise<any>;

  /**
   * Copy the given files/directories to the given destination
   */
  copy(paths: string[], destination: string): Promise<any>;

  /**
   * Move the given files/directories to the given destination
   */
  move(paths: string[], destination: string): Promise<any>;

  /**
   * Upload the given files into the given directory path
   */
  upload(files: File[], directoryPath: string): Promise<any>;
}
```

Now let's import our interface in our `FileManagerService` file.

```ts
// file-manager-service.ts
import FileManagerServiceInterface from "../types/FileManagerServiceInterface";

export class FileManagerService implements FileManagerServiceInterface {}
```

Of course now the Typescript complier will complain that we didn't implement these methods, so let's do it.

But before we do this, let's install first [Faker](https://github.com/faker-js/faker) so we can fake some data.

`yarn add -D @faker-js/faker`

Now let's return to our service class.

```ts
import FileManagerServiceInterface from "../types/FileManagerServiceInterface";

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

We made our promise to mock `axios` response shape so once we're done with the mockup api, we can easily replace it with real api at anytime.

> If you've a real API to work with, i'll be pleased to use in the series :)

and the list method will return list of `nodes` as array.

Now let's create `listNodes` function from faker, it will be created in `utils` directory under `data.ts` file.

```ts
// file-manager/utils/data.ts
import { faker } from "@faker-js/faker";
import { DirectoryNode, Node } from "../types/FileManager.types";

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

  if (node.isDirectory) {
    (node as DirectoryNode).children = listNodes(3);
  }

  return node;
}

export function listNodes(maxNodes = 10): Node[] {
  return faker.datatype
    .array(faker.datatype.number({ min: 1, max: maxNodes }))
    .map(newNode);
}
```

In the second function `listNodes` it will create list of nodes randomly and it accepts one parameter, the max generated nodes, this is important so we don't get in infinity loop when generating children nodes as we maximize it with only 3 children nodes (in `newNode` function).

`newNode` function is used to generated random node data, we set the node that it can be `Node` or `DirectoryNode`.

Actually, let's work only with `Node` and set the `children` property inside it, so if the node is directory it can have children.

Let's navigate to `types/FileManager.types.ts` to remove `FileNode` and `DirectoryNode` then update our `Node`.

```ts
// file-manager/types/FileManager.types.ts
/**
 * File Manager node is the primary data structure for the File Manager.
 * It can be a directory or a file.
 * It contains the following properties:
 */
export type Node = {
  /**
   * Node Name
   */
  name: string;
  /**
   * Node full path to root
   */
  path: string;
  /**
   * Node size in bits
   */
  size: number;
  /**
   * Is node directory
   */
  isDirectory: boolean;
  /**
   * Node children
   * This should be present (event with empty array) if the node is directory
   */
  children?: Node[];
};
```

This will make our code less confused so do us lol, so the node can have two types of shapes, a directory shape that contains children and a file shape that does not have children.

Let's go to `FileManager` class and update the current directory node.

```ts
// 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;
}
```

Finally let's update again our `utils/data.ts` file

```ts
// file-manager/utils/data.ts
import { faker } from "@faker-js/faker";
import { Node } from "../types/FileManager.types";

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

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

  return node;
}

export function listNodes(maxNodes = 10): Node[] {
  return faker.datatype
    .array(faker.datatype.number({ min: 1, max: maxNodes }))
    .map(newNode);
}
```

Now our code is much cleaner.

> **Pro Tip**: you don't get the perfect code from the first time, that's why we're going to enhance and develop our code gradually as we progress in our project.

Heading back to our File Manager Service.

```ts
import FileManagerServiceInterface from "../types/FileManagerServiceInterface";
import { listNodes } from "../utils/data";

export class FileManagerService implements FileManagerServiceInterface {
  /**
   * {@inheritDoc}
   */
  public list(directoryPath: string): Promise<any> {
    return new Promise(resolve => {
      resolve({
        data: {
          nodes: listNodes(),
        },
      });
    });
  }
  
  /**
   * {@inheritDoc}
   */
  public createDirectory(directoryName: string, saveTo: string): Promise<any> {
    throw new Error("Method not implemented.");
  }
  /**
   * {@inheritDoc}
   */
  public delete(paths: string[]): Promise<any> {
    throw new Error("Method not implemented.");
  }
  /**
   * {@inheritDoc}
   */
  public rename(path: string, newPath: string): Promise<any> {
    throw new Error("Method not implemented.");
  }
  /**
   * {@inheritDoc}
   */
  public copy(paths: string[], destination: string): Promise<any> {
    throw new Error("Method not implemented.");
  }
  /**
   * {@inheritDoc}
   */
  public move(paths: string[], destination: string): Promise<any> {
    throw new Error("Method not implemented.");
  }
  /**
   * {@inheritDoc}
   */
  public upload(files: File[], directoryPath: string): Promise<any> {
    throw new Error("Method not implemented.");
  }
}
```

I made the rest of the code to be auto injected using `vscode` feature or so, so it doesn't pop any compiler error.

So far so good, i'll stop at the moment at this point.

You can see the results in the `HomePage` component.

Head to the component and paste the following code inside it then see the results in the console.

```tsx
// home/components/HomePage/HomePage.tsx
import Helmet from "@mongez/react-helmet";
import { FileManagerService } from "app/file-manager/services/file-manager-service";

const fileManager = new FileManagerService();

export default function HomePage() {
  fileManager.list("/").then(response => {
    console.log(response.data);
  });
  return (
    <>
      <Helmet title="home" appendAppName={false} />
      <h1>Welcome To Home Page</h1>
    </>
  );
}
```

## Article Repository

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

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