Let's create a React File Manager Chapter XI: Navigating between directories
Tags: react, typescript, mongez, javascript
Published: 2022-09-14 11:27:53

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

So we can see proper results, we need to make a file to fetch data from, as pseudo data, we will use `data.json` file, it will contain an array of directories and files, each directory will have an array of files and directories, and each file will have a name and a type, we will use this data to create our file manager.

Create `utils/data.json` file and insert inside it the following code:

```json
{
  "path": "/",
  "name": "/",
  "size": 70533,
  "isDirectory": true,
  "children": [
    {
      "path": "/Library",
      "name": "Library",
      "size": 68201,
      "isDirectory": true,
      "children": [
        {
          "path": "/var/log/rhodium_lodge.lwp",
          "name": "var",
          "size": 87444,
          "isDirectory": false
        }
      ]
    },
    {
      "path": "/media/intelligent.jnlp",
      "name": "media",
      "size": 1236,
      "isDirectory": false
    },
    {
      "path": "/var/log/fire_furthermore_purity.mp4v",
      "name": "var",
      "size": 95179,
      "isDirectory": false
    },
    {
      "path": "/local/log",
      "name": "local",
      "size": 9739,
      "isDirectory": true,
      "children": [
        {
          "path": "/opt",
          "name": "opt",
          "size": 68469,
          "isDirectory": true,
          "children": [
            {
              "path": "/usr/libdata/southwest.vtf",
              "name": "usr",
              "size": 75879,
              "isDirectory": false
            }
          ]
        }
      ]
    },
    {
      "path": "/etc/mail/bossy_fess_compress.mvb",
      "name": "etc",
      "size": 47549,
      "isDirectory": false
    },
    {
      "path": "/usr/bin/arkansas_suv.mus",
      "name": "usr",
      "size": 98329,
      "isDirectory": false
    },
    {
      "path": "/home/concept_alliance_account.avi",
      "name": "home",
      "size": 37505,
      "isDirectory": false
    },
    {
      "path": "/private/tmp",
      "name": "private",
      "size": 96386,
      "isDirectory": true,
      "children": [
        {
          "path": "/opt/bin/program_rupee_views.ifb",
          "name": "opt",
          "size": 36417,
          "isDirectory": false
        }
      ]
    },
    {
      "path": "/home/qui_account_joule.srt",
      "name": "home",
      "size": 59250,
      "isDirectory": false
    },
    {
      "path": "/tmp",
      "name": "tmp",
      "size": 79276,
      "isDirectory": true,
      "children": [
        {
          "path": "/opt/share/east_beauty.sgl",
          "name": "opt",
          "size": 70401,
          "isDirectory": false
        }
      ]
    },
    {
      "path": "/home",
      "name": "home",
      "size": 77920,
      "isDirectory": true,
      "children": [
        {
          "path": "/private/var",
          "name": "private",
          "size": 50803,
          "isDirectory": true,
          "children": [
            {
              "path": "/var/mail",
              "name": "var",
              "size": 51658,
              "isDirectory": true,
              "children": [
                {
                  "path": "/Users",
                  "name": "Users",
                  "size": 66068,
                  "isDirectory": true,
                  "children": [
                    {
                      "path": "/media/bespoke_connecticut.wmd",
                      "name": "media",
                      "size": 92243,
                      "isDirectory": false
                    },
                    {
                      "path": "/usr/include",
                      "name": "usr",
                      "size": 59751,
                      "isDirectory": true,
                      "children": [
                        {
                          "path": "/usr/local/bin",
                          "name": "usr",
                          "size": 86273,
                          "isDirectory": true,
                          "children": [
                            {
                              "path": "/home/account.sc",
                              "name": "home",
                              "size": 28456,
                              "isDirectory": false
                            }
                          ]
                        },
                        {
                          "path": "/lost+found",
                          "name": "lost+found",
                          "size": 95753,
                          "isDirectory": true,
                          "children": [
                            {
                              "path": "/net/invoice_connecting_kuwait.ssf",
                              "name": "net",
                              "size": 48547,
                              "isDirectory": false
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ]
            },
            {
              "path": "/usr/bin/sdd_shoes_lead.ddf",
              "name": "usr",
              "size": 86112,
              "isDirectory": false
            }
          ]
        },
        {
          "path": "/net/steel_paradigms_generate.geo",
          "name": "net",
          "size": 5962,
          "isDirectory": false
        }
      ]
    },
    {
      "path": "/src",
      "name": "src",
      "size": 41634,
      "isDirectory": true,
      "children": [
        {
          "path": "/usr/libdata",
          "name": "usr",
          "size": 39378,
          "isDirectory": true,
          "children": [
            {
              "path": "/usr/lib",
              "name": "usr",
              "size": 20505,
              "isDirectory": true,
              "children": [
                {
                  "path": "/root/aggregate.igm",
                  "name": "root",
                  "size": 65337,
                  "isDirectory": false
                },
                {
                  "path": "/home/user/fussy_integrate.m21",
                  "name": "home",
                  "size": 69135,
                  "isDirectory": false
                }
              ]
            },
            {
              "path": "/var/mail",
              "name": "var",
              "size": 15543,
              "isDirectory": true,
              "children": [
                {
                  "path": "/etc/ppp/response_savings_southeast.xaml",
                  "name": "etc",
                  "size": 267,
                  "isDirectory": false
                }
              ]
            }
          ]
        },
        {
          "path": "/sbin",
          "name": "sbin",
          "size": 88790,
          "isDirectory": true,
          "children": [
            {
              "path": "/etc/periodic",
              "name": "etc",
              "size": 23663,
              "isDirectory": true,
              "children": [
                {
                  "path": "/media",
                  "name": "media",
                  "size": 81614,
                  "isDirectory": true,
                  "children": [
                    {
                      "path": "/Network/field.xer",
                      "name": "Network",
                      "size": 44406,
                      "isDirectory": false
                    },
                    {
                      "path": "/home/user/dir",
                      "name": "home",
                      "size": 89296,
                      "isDirectory": true,
                      "children": [
                        {
                          "path": "/selinux/orchestrator.xsm",
                          "name": "selinux",
                          "size": 84414,
                          "isDirectory": false
                        },
                        {
                          "path": "/media",
                          "name": "media",
                          "size": 68960,
                          "isDirectory": true,
                          "children": [
                            {
                              "path": "/usr/monstrous_moratorium_response.ttl",
                              "name": "usr",
                              "size": 57457,
                              "isDirectory": false
                            },
                            {
                              "path": "/usr/obj/movies.exr",
                              "name": "usr",
                              "size": 52956,
                              "isDirectory": false
                            }
                          ]
                        }
                      ]
                    }
                  ]
                },
                {
                  "path": "/opt/include",
                  "name": "opt",
                  "size": 24011,
                  "isDirectory": true,
                  "children": [
                    {
                      "path": "/usr/libdata",
                      "name": "usr",
                      "size": 57283,
                      "isDirectory": true,
                      "children": [
                        {
                          "path": "/var/log/sievert_west_guilder.cdkey",
                          "name": "var",
                          "size": 36818,
                          "isDirectory": false
                        },
                        {
                          "path": "/usr/ports",
                          "name": "usr",
                          "size": 62616,
                          "isDirectory": true,
                          "children": [
                            {
                              "path": "/Applications/kyat_saturate_home.grv",
                              "name": "Applications",
                              "size": 21664,
                              "isDirectory": false
                            },
                            {
                              "path": "/root",
                              "name": "root",
                              "size": 10739,
                              "isDirectory": true,
                              "children": [
                                {
                                  "path": "/private/var",
                                  "name": "private",
                                  "size": 90775,
                                  "isDirectory": true,
                                  "children": [
                                    {
                                      "path": "/Library",
                                      "name": "Library",
                                      "size": 85881,
                                      "isDirectory": true,
                                      "children": [
                                        {
                                          "path": "/mnt/cobalt_bicycle_earum.wsdl",
                                          "name": "mnt",
                                          "size": 55769,
                                          "isDirectory": false
                                        },
                                        {
                                          "path": "/etc",
                                          "name": "etc",
                                          "size": 8215,
                                          "isDirectory": true,
                                          "children": [
                                            {
                                              "path": "/usr/obj/virginia_manager.ait",
                                              "name": "usr",
                                              "size": 3988,
                                              "isDirectory": false
                                            }
                                          ]
                                        }
                                      ]
                                    }
                                  ]
                                },
                                {
                                  "path": "/lost+found/deposit_auxiliary.evy",
                                  "name": "lost+found",
                                  "size": 14874,
                                  "isDirectory": false
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    },
                    {
                      "path": "/net/exploit_wooden_lead.rif",
                      "name": "net",
                      "size": 64496,
                      "isDirectory": false
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "path": "/usr/libdata",
      "name": "usr",
      "size": 76429,
      "isDirectory": true,
      "children": [
        {
          "path": "/etc/mail/riverside_associate.vst",
          "name": "etc",
          "size": 74947,
          "isDirectory": false
        }
      ]
    },
    {
      "path": "/tmp/uphold_input.ma",
      "name": "tmp",
      "size": 5102,
      "isDirectory": false
    }
  ]
}
```

Now let's import it in our service and remove the old generators.

```diff
// file-manager-service.ts
- import { newDirectoryNode } from "../utils/data";
+ import rootNode from "./../utils/data.json";


  /**
   * {@inheritDoc}
   */
  public list(directoryPath: string): Promise<any> {
    return new Promise(resolve => {
      resolve({
        data: {
-          node: newDirectoryNode(),
+          node: rootNode,
        },
      });
    });
  }
```

Now we can have stable nodes, but we need to make it more real, let's create a function that fetch the proper directory based on the given directory path.

Create `fetchDirectory` function in `utils/helpers.ts` file.

```ts
// helpers.ts
import { Node } from "../types/FileManager.types";
import rootNode from "./data.json";

export default function fetchNode(
  directoryPath: string,
  node?: Node,
): Node | undefined {
  if (!node) {
    node = rootNode;
  }

  if (node.path === directoryPath) {
    return node;
  } else if (node.isDirectory && node.children) {
    for (const child of node.children) {
      const foundNode = fetchNode(directoryPath, child);

      if (foundNode) {
        return foundNode;
      }
    }
  }
}
```

A simple recursion function here to check if the given node is the one we are looking for, if not, we will check the children of the node and so on.

Let's go back to our service again and update its code.

```tsx
// file-manager-service.ts
import FileManagerServiceInterface from "../types/FileManagerServiceInterface";
import fetchNode from "../utils/helpers";

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

## Before we move on

Again, as i mentioned in previous article, we're developing and scaling up our application as it will be more complicated over the next articles, but we're walking step by step so you know where we are now and where we're going.

## Sidebar navigation

Now let's add the ability to navigate between directories, we will do that by adding a click handler to the sidebar node, when the user clicks on a node, we will set the current directory node to the clicked node.

```diff
// SidebarNode.tsx

  return (
    <>
      <NavLink
        {...navProps}
        active={isActiveNode}
+        onClick={() => fileManager.load(node.path)}
        label={
          <>
            <IconWrapper>{icon}</IconWrapper>
            <span>{node.name}</span>
          </>
        }
      />
    </>
  );
```

Now we need to display a loader to tell that the file manager is loading a content, here we are going to introduce our secret weapon: `Events`

## File Manager Events

So we need to know from the file manager when it is loading a directory, so we can display a loader, we can do that by adding an event to the file manager, so we can listen to it from the sidebar node.

```ts
// FileManager.types.tsx

/**
 * File manager events
 */
export type FileManagerEvents = "loading" | "load" | "directoryChange";
```

`loading` event will be triggered before loading directory.

`load` event will be triggered after loading directory.

`directoryChange` event will be triggered after changing current directory.

Now let's add an event listener in our file manager.

```ts
// FileManager.tsx
import events, { EventSubscription } from "@mongez/events";

...
  /**
   * Add event listener to the given event
   */
  public on(event: FileManagerEvents, callback: any): EventSubscription {
    return events.subscribe(`file-manger.${event}`, callback);
  }
```

We're going to use [Mongez Events](https://github.com/hassanzohdy/mongez-events) to manage events.

Now let's add trigger method to trigger the event.

```tsx
// FileManager.tsx
...
  /**
   * Trigger the given event
   */
  public trigger(event: FileManagerEvents, ...args: any[]): void {
    events.trigger(`file-manger.${event}`, ...args);
  }
```

Now let's add trigger the events from `load` method.

```tsx
// FileManager.tsx
...
  /**
   * Load the given path
   */
  public load(path: string): Promise<Node> {
    // trigger loading event
    this.trigger("loading");

    return new Promise((resolve, reject) => {
      fileManagerService
        .list(path)
        .then(response => {
          // trigger load event as the directory has been loaded successfully.
          this.trigger("load", response.data.node);
          this.currentDirectoryPath = path;
          this.currentDirectoryNode = response.data.node;
          // if the current directory is not as the same loaded directory path,
          // then we'll trigger directory changed event.
          if (response.data.node.path !== this.currentDirectoryPath) {
            this.trigger("directoryChange", this.currentDirectoryNode);
          }

          resolve(this.currentDirectoryNode as Node);
        })
        .catch(reject);
    });
  }
```

`loading` event will be triggered before loading directory, and once the response is sent successfully to us, we'll trigger `load` event, and if the current directory is not as the same loaded directory path, then we'll trigger `directoryChange` event.

## Listening to current directory change

Now let's add an event listener to current directory change in sidebar node, if the current directory is changed then update the active state.

```tsx
// SidebarNode.tsx
...
  useEffect(() => {
    fileManager.on("directoryChange", (newCurrentDirectory: Node) => {
      if (newCurrentDirectory.path !== node.path && isActiveNode) {
        setIsActiveNode(false);
      } else if (newCurrentDirectory.path === node.path && !isActiveNode) {
        setIsActiveNode(true);
      }
    });
  }, [fileManager, isActiveNode, node.path]);
```

So we have two cases here, first one is current node is active but new current directory node's path is not the same as the node path, then we'll set it to be false.

Second case the current node is not active but new current directory node's path is the same as the node path, then we'll set it to be true.

Now we'll remove the old `useEffect` that was listening for `fileManager.currentDirectoryNode` to not make any conflicts.

So the final sidebar node should look like

```tsx
// SidebarNode.tsx
import { NavLink, NavLinkProps } from "@mantine/core";
import { IconFolder } from "@tabler/icons";
import { useEffect, useState } from "react";
import useFileManager from "../../../hooks/useFileManager";
import { Node } from "../../../types/FileManager.types";
import { IconWrapper } from "./SidebarNode.styles";

export type SidebarNodeProps = {
  node: Node;
  icon?: React.ReactNode;
  navProps?: Partial<NavLinkProps>;
};

export default function SidebarNode({
  icon,
  node,
  navProps = {},
}: SidebarNodeProps) {
  const fileManager = useFileManager();

  const [isActiveNode, setIsActiveNode] = useState(
    node === fileManager.currentDirectoryNode,
  );

  useEffect(() => {
    const event = fileManager.on(
      "directoryChange",
      (newCurrentDirectory: Node) => {
        if (newCurrentDirectory.path !== node.path && isActiveNode) {
          setIsActiveNode(false);
        } else if (newCurrentDirectory.path === node.path && !isActiveNode) {
          setIsActiveNode(true);
        }
      },
    );
  }, [fileManager, isActiveNode, node]);

  return (
    <>
      <NavLink
        {...navProps}
        active={isActiveNode}
        onClick={() => fileManager.load(node.path)}
        label={
          <>
            <IconWrapper>{icon}</IconWrapper>
            <span>{node.name}</span>
          </>
        }
      />
    </>
  );
}

SidebarNode.defaultProps = {
  icon: <IconFolder fill="#31caf9" />,
};
```

> We need to unsubscribe from the event listener when the component is unmounted or the effect dependencies are updated.

```diff
  useEffect(() => {
    const event = fileManager.on(
      "directoryChange",
      (newCurrentDirectory: Node) => {
        if (newCurrentDirectory.path !== node.path && isActiveNode) {
          setIsActiveNode(false);
        } else if (newCurrentDirectory.path === node.path && !isActiveNode) {
          setIsActiveNode(true);
        }
      },
    );

+    return () => event.unsubscribe();
  }, [fileManager, isActiveNode, node]);
```

Now we can successfully navigate between sidebar links.

![File Manager](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6eguyydu2r8etzppqqc.png)

![File Manager](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a60dtvcenntzybefcmmj.png)

In our next chapter, we will make progress bar to indicate loading progress.

## The Power Of Events

If you noticed, we added an event to each sidebar node, but the catch here only the affected sidebar nodes will be re-rendered, so if we have 1000 nodes, and we click on one of them, only the affected node will be re-rendered, and the rest will be untouched.

## Article Repository

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

> 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.