Let's create a React File Manager Chapter XIV: File Manager Body
Tags: react, typescript, mongez, javascript
Published: 2022-09-15 08:12:16

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

So we've done a lot of work in the previous chapters in the sidebar and loaders, and now it's time to put some efforts in the file manager body.

## Little update in progress bar loader

We'll just display a white background if the progress is `0`.

```tsx
// LoadingProgressBar.tsx
...
  return (
    <Progress
      size="lg"
      value={progress}
      striped
    // 👇🏻  We'll add the styles prop to style the root
      styles={{
        root: {
          backgroundColor: progress === 0 ? "white" : undefined,
        },
      }}
      label={progress > 0 ? `${progress}%` : undefined}
      color={mapProgressColor()}
      animate
    />
  );
```

## Separating The Overlay in its own component

As we know now the overlay appears on the file manager loading state, so we'll create a separate component to be only rendered when the loading happen.

So let's create a new file `Content/ContentOverlay.tsx` and add the following code:

```tsx
// ContentOverlay.tsx
import { LoadingOverlay } from "@mantine/core";
import { useLoading } from "app/file-manager/hooks";

export default function ContentOverlay() {
  const isLoading = useLoading();
  return <LoadingOverlay visible={isLoading} overlayBlur={2} />;
}
```

Now let's import it in `Content.tsx`.

```tsx
// Content.tsx
import { Card } from "@mantine/core";
import { ContentWrapper } from "./Content.styles";
import ContentOverlay from "./ContentOverlay"; 👈🏻

export default function Content() {
  return (
    <>
      <Card shadow="sm">
        <ContentWrapper>
          <ContentOverlay /> 👈🏻
        </ContentWrapper>
      </Card>
    </>
  );
}
```

## Nodes List

Now its time for the big part, the nodes list, so let's create a new file `Content/NodesList.tsx` That will render all nodes.

But we need to split nodes into two arrays, one for folders and one for files.

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

export default function NodesList() {
  const kernel = useKernel();

  const currentDirectoryNode = kernel.currentDirectoryNode;

  const [directories, files] = useMemo(() => {
    const node = currentDirectoryNode;

    if (!node || !node.children?.length) return [[], []];

    return [
      node.children.filter(node => node.isDirectory),
      node.children.filter(node => !node.isDirectory),
    ];
  }, [currentDirectoryNode]);

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

We created a memo to list all directories nodes and files nodes as well, but we want to make sure first there is current directory node to be loaded and it has children as well.

Now you should see something like this:

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

## Directory Nodes and File Nodes

So we need to make full control over each node, either in the directory node list or in the file node list, so we'll create two components `DirectoryNode` and `FileNode` to handle each node, they will be created in `Content/ContentNode` directory.

```tsx
// Content/ContentNode/DirectoryNode.tsx

import { DirectoryNodeProps } from "./ContentNode.types";

export default function DirectoryNode({ node }: DirectoryNodeProps) {
    return <>{node.name}</>;
}
```

```tsx
// Content/ContentNode/FileNode.tsx

import { FileNodeProps } from "./ContentNode.types";

export default function FileNode({ node }: FileNodeProps) {
    return <>{node.name}</>;
}
```

Let's create `ContentNode.types.ts` to hold the types for both components.

```tsx
// Content/ContentNode/ContentNode.types.ts
import { Node } from "app/file-manager/Kernel";

export type FileNodeProps = {
  node: Node;
};

export type DirectoryNodeProps = {
  node: Node;
};
```

All what we added in the props is just the node object.

Now let's create an index file to export all components in the directory.

```tsx
// Content/ContentNode/index.ts

export { default as DirectoryNode } from "./DirectoryNode";
export { default as FileNode } from "./FileNode";
```

Now let's import these nodes in our NodesList

```tsx
// NodesList.tsx
import { Grid } from "@mantine/core";
import { useKernel } from "app/file-manager/hooks";
import { useMemo } from "react";
👉🏻 import { DirectoryNode, FileNode } from "./ContentNode";

export default function NodesList() {
  const kernel = useKernel();

  const currentDirectoryNode = kernel.currentDirectoryNode;

  const [directories, files] = useMemo(() => {
    const node = currentDirectoryNode;

    if (!node || !node.children?.length) return [[], []];

    return [
      node.children.filter(node => node.isDirectory),
      node.children.filter(node => !node.isDirectory),
    ];
  }, [currentDirectoryNode]);

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

## useCurrentDirectoryNode Hook

Let's create a new hook that returns current directory node, and also listen for any change for it, if so then re-render any component that uses this hook.

```tsx
// hooks/useCurrentDirectoryNode.ts

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

export default function useCurrentDirectoryNode() {
  const kernel = useKernel();
  const [node, setNode] = useState<Node | undefined>(
    kernel.currentDirectoryNode,
  );

  useEffect(() => {
    const event = kernel.on("directoryChange", setNode);

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

  return node;
}
```

We simply get the current directory node from the kernel and set it in a `state` then we watched for any change that happen to current node change, in that case we'll update the state thus the component will be re-rendered.

### Using useEvent

There is another good hook to use with Events, `useEvent` that will handle the event `unsubscribe` for you, so you don't need to worry about it.

```tsx
// hooks/useCurrentDirectoryNode.ts

👉🏻 import { useEvent } from "@mongez/react";
import { Node } from "app/file-manager/Kernel";
import { useState } from "react";
import useKernel from "./useKernel";

export default function useCurrentDirectoryNode() {
  const kernel = useKernel();
  const [node, setNode] = useState<Node | undefined>(
    kernel.currentDirectoryNode,
  );

👉🏻   useEvent(() => kernel.on("directoryChange", setNode));

  return node;
}
```

This will do exactly the same effect as the previous code, but it's more readable and you don't need to worry about the unsubscribe event.

> Note that the `useEvent` callback must return the Event Subscription.

Now let's update our `NodesList` to listen for changes in the current directory node so it makes a new render.

```tsx
// NodesList.tsx
import { Grid } from "@mantine/core";
// 👇🏻 import the hook
import { useCurrentDirectoryNode } from "app/file-manager/hooks";
import { useMemo } from "react";
import { DirectoryNode, FileNode } from "./ContentNode";

export default function NodesList() {
   // 👇🏻 remove the useKernel hook and replace it with useCurrentDirectoryNode
   
  ❌ const kernel = useKernel();

  ❌ const currentDirectoryNode = kernel.currentDirectoryNode;

  ✅ const currentDirectoryNode = useCurrentDirectoryNode();

  const [directories, files] = useMemo(() => {
    const node = currentDirectoryNode;

    if (!node || !node.children?.length) return [[], []];

    return [
      node.children.filter(node => node.isDirectory),
      node.children.filter(node => !node.isDirectory),
    ];
  }, [currentDirectoryNode]);

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

## Enhancing UI for DirectoryNode and FileNode

Now let's make some nice UI for each node, we'll use `NavLink` component from Mantine and also some icons as well.

```tsx
// DirectoryNode.tsx
import { NavLink, useMantineTheme } from "@mantine/core";
import { IconFolder } from "@tabler/icons";
import { useKernel } from "app/file-manager/hooks";
import { DirectoryNodeProps } from "./ContentNode.types";

export default function DirectoryNode({ node }: DirectoryNodeProps) {
    // 👇🏻 get the theme
  const theme = useMantineTheme();
  const kernel = useKernel();

  return (
    // 👇🏻 use NavLink component
    <NavLink
      style={{
        // 👇🏻 center the node and make the cursor to be default
        textAlign: "center",
        cursor: "default",
      }}
      // 👇🏻 add the icon, we'll use the IconFolder and give it some good styles from the theme
      label={
        <>        
          <IconFolder
            fill={theme.colors.blue[4]}
            strokeWidth={1.5}
            color={theme.colors.blue[9]}
            size={40}
          />
          <div>{node.name}</div>
        </>
      }
    />
  );
}
```

Let's do the same in the `FileNode` component.

```tsx
// FileNode.tsx
import { NavLink, useMantineTheme } from "@mantine/core";
import { IconFileInfo as Icon } from "@tabler/icons";
import { FileNodeProps } from "./ContentNode.types";

export default function FileNode({ node }: FileNodeProps) {
  const theme = useMantineTheme();
  return (
    <NavLink
      style={{
        textAlign: "center",
        cursor: "default",
      }}
      label={
        <>
          <Icon
            fill={theme.colors.green[4]}
            strokeWidth={1.5}
            color={theme.colors.green[9]}
            size={40}
          />
          <div>{node.name}</div>
        </>
      }
    />
  );
}
```

Now the final ui will look like this:

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

## Open Directory on Double Click

Let's add a little feature, when the user double click on the Directory node, let's open it.

```tsx
// DirectoryNode.tsx
import { NavLink, useMantineTheme } from "@mantine/core";
import { IconFolder } from "@tabler/icons";
import { useKernel } from "app/file-manager/hooks";
import { DirectoryNodeProps } from "./ContentNode.types";

export default function DirectoryNode({ node }: DirectoryNodeProps) {
  const theme = useMantineTheme();
  const kernel = useKernel();

  return (
    <NavLink
      style={{
        textAlign: "center",
        cursor: "default",
      }}
      // 👇🏻 add the onDoubleClick event
      onDoubleClick={() => kernel.load(node.path)}
      label={
        <>
          <IconFolder
            fill={theme.colors.blue[4]}
            strokeWidth={1.5}
            color={theme.colors.blue[9]}
            size={40}
          />
          <div>{node.name}</div>
        </>
      }
    />
  );
}
```

## Article Repository

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

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