Let's create a React File Manager Chapter VII: Listing Sidebar
Hasan Zohdy
Posted on September 13, 2022
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]);
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]);
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]);
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]);
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: "/",
};
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>
</>
);
}
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>
</>
);
}
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>
Now you should see something like this:
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>
</>
);
}
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>
</>
);
}
Now it looks like this:
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.
Posted on September 13, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.