Creating a file/folder structure in React.
In this Blog I am just breaking down my approach of building a file and folder structure

Faisal Husain

Ok lets start breaking down the problem
I will approach this problem in 4 steps:
- Create a data file - This mean in most of the cases I have to prepare this file so that I cant display file or folder on screen
- Displaying file/folder - At this step we will tackle how to display date in tree format on screen.
- Expanding/Collapsible Functionality - Expand or Collapse Folder as file cannot be expanded 😅
- Adding File/Folder - Adding file or folder to tree.
- Deleting File/Folder - Deleting file or folder from tree.
Step 1 - Create a data file.
So just think how show a data file look like. It will be array of objects and each object will have an id, name , boolean of isFolder and childrens (if its a folder)
export const data = [
{
id: "1",
isFolder: true,
name: "public",
children: [
{
id: "2",
isFolder: false,
name: "index.html",
},
],
},
{
id: "3",
isFolder: true,
name: "src",
children: [
{
id: "4",
isFolder: true,
name: "components",
children: [
{
id: "5",
isFolder: false,
name: "FileExplorer.tsx",
},
],
},
{
id: "6",
isFolder: true,
name: "data",
children: [
{
id: "7",
isFolder: false,
name: "data.ts",
},
],
},
{
id: "8",
isFolder: true,
name: "pages",
children: [
{
id: "9",
isFolder: false,
name: "Home.tsx",
},
],
},
{
id: "10",
isFolder: false,
name: "App.tsx",
},
{
id: "11",
isFolder: false,
name: "index.tsx",
},
{
id: "12",
isFolder: false,
name: "styles.css",
},
],
},
{
id: "13",
isFolder: false,
name: "package.json",
},
{
id: "14",
isFolder: false,
name: "tsconfig.json",
},
];
Try to create this on your own as this take time.
Step 2 - Displaying file/folder.
📁 public
📄 index.html
📁 src
📁 components
📄 FileExplorer.tsx
📁 data
📄 data.ts
📁 pages
📄 Home.tsx
📄 App.tsx
📄 index.tsx
📄 styles.css
📄 package.json
📄 tsconfig.json
"use client"
import { useState } from "react";
import { data, FileNode } from "./data";
const FolderStructure = ({ data }: { data: FileNode[] }) => {
return (
<div>
{data.map((node: FileNode) => (
<div key={node.id} className="pl-4 cursor-pointer">
{node.isFolder ? "📁" :"📄"} {node.name}
{node.children && <FolderStructure data={node.children} />}
</div>
))}
</div>
)
}
const Ff = () => {
const [initialData, setInitialData] = useState(data)
return (
<div className="w-[calc(100vw-2rem)] md:w-[700px] bg-black p-3 ">
<div className="flex items-center justify-center">
<FolderStructure data={initialData} />
</div>
</div>
);
};
export default Ff;
Step 3 - Expanding/Collapsible Functionality.
📁 public
📁 src
📄 package.json
📄 tsconfig.json
"use client"
import { useState } from "react";
import { data, FileNode } from "./data";
const FolderStructure = ({ data }: { data: FileNode[] }) => {
const [expanded, setExpanded] = useState<{ [key: string]: boolean }>({})
return (
<div>
{data.map((node: FileNode) => (
<div key={node.id} className="pl-4 cursor-pointer">
<div onClick={()=>{
setExpanded((prev)=>({
...prev,
[node.id]: !prev[node.id]
}))
}}>
{node.isFolder ? (expanded?.[node.id] ? "📂" :"📁") : "📄"} {node.name}
</div>
{ expanded?.[node.id] && node.children && <FolderStructure data={node.children} />}
</div>
))}
</div>
)
}
const Ff = () => {
const [initialData, setInitialData] = useState(data)
return (
<div className="w-[calc(100vw-2rem)] md:w-[700px] bg-black p-3 border-zinc-800 border-2 rounded-2xl ">
<div className="flex items-center justify-center ">
<FolderStructure data={initialData} />
</div>
</div>
);
};
export default Ff;
Step 4 - Adding File/Folder.
📁public
📁src
📄package.json
📄tsconfig.json
"use client"
import { Children, useState } from "react";
import { data, FileNode } from "./data";
import { Button } from "@/components/ui/button";
const FolderStructure = ({ data, addFolder }: { data: FileNode[], addFolder: (parentId: any) => void }) => {
const [expanded, setExpanded] = useState<{ [key: string]: boolean }>({})
return (
<div>
{data.map((node: FileNode) => (
<div key={node.id} className="pl-4 cursor-pointer">
<div >
<span onClick={(e) => {
setExpanded((prev) => ({
...prev,
[node.id]: !prev[node.id]
}))
}}>
{node.isFolder ? (expanded?.[node.id] ? "📂" : "📁") : "📄"}
</span>
<span>
{node.name}
</span>
<Button onClick={(e) => {
e.preventDefault()
addFolder(node.id)
}}>
➕
</Button>
</div>
{expanded?.[node.id] && node.children && <FolderStructure data={node.children} addFolder={addFolder} />}
</div>
))}
</div>
)
}
const Ffs = () => {
const [initialData, setInitialData] = useState(data)
const addFolder = (parentId: any) => {
const updateTree = (data: any) => {
return data.map((node: FileNode) => {
if (node.id === parentId) {
return {
...node,
children: [
...(node.children || []),
{
id: Date.now().toString(),
name: "New Folder",
isFolder: true,
children: []
}
]
}
}
if (node.children) {
return {
...node,
children: updateTree(node.children)
}
}
return node
})
}
setInitialData((pre) => updateTree(pre))
}
return (
<div className="w-[calc(100vw-2rem)] md:w-[700px] bg-black p-3 border-zinc-800 border-2 rounded-2xl ">
<div className="flex items-center justify-center ">
<FolderStructure data={initialData} addFolder={addFolder} />
</div>
</div>
);
};
export default Ffs;
Step 5 - Deleting File/Folder
📁public
📁src
📄package.json
📄tsconfig.json
"use client"
import { Children, useState } from "react";
import { data, FileNode } from "./data";
import { Button } from "@/components/ui/button";
const FolderStructure = ({ data, addFolder, deleteFileorFolder }: { data: FileNode[], addFolder: (parentId: any) => void, deleteFileorFolder: (parentId: any) => void }) => {
const [expanded, setExpanded] = useState<{ [key: string]: boolean }>({})
return (
<div>
{data.map((node: FileNode) => (
<div key={node.id} className="pl-4 cursor-pointer">
<div >
<span onClick={(e) => {
setExpanded((prev) => ({
...prev,
[node.id]: !prev[node.id]
}))
}}>
{node.isFolder ? (expanded?.[node.id] ? "📂" : "📁") : "📄"}
</span>
<span>
{node.name}
</span>
<Button onClick={(e) => {
e.preventDefault()
addFolder(node.id)
}}>
➕
</Button>
<Button onClick={(e) => {
e.preventDefault()
deleteFileorFolder(node.id)
}}>
🗑️
</Button>
</div>
{expanded?.[node.id] && node.children && <FolderStructure data={node.children} addFolder={addFolder} deleteFileorFolder={deleteFileorFolder} />}
</div>
))}
</div>
)
}
const Ffs = () => {
const [initialData, setInitialData] = useState(data)
const addFolder = (parentId: any) => {
const updateTree = (data: any) => {
return data.map((node: FileNode) => {
if (node.id === parentId) {
return {
...node,
children: [
...(node.children || []),
{
id: Date.now().toString(),
name: "New Folder",
isFolder: true,
children: []
}
]
}
}
if (node.children) {
return {
...node,
children: updateTree(node.children)
}
}
return node
})
}
setInitialData((pre) => updateTree(pre))
}
const deleteFileorFolder = (parentId: any) => {
const updateTree = (data: any) => {
return data.map((node: FileNode) => {
if (node.id === parentId) {
return null
}
if (node.children) {
return {
...node,
children: updateTree(node.children)
}
}
return node
}).filter(Boolean);
}
setInitialData((pre) => updateTree(pre))
}
return (
<div className="w-[calc(100vw-2rem)] md:w-[700px] bg-black p-3 border-zinc-800 border-2 rounded-2xl ">
<div className="flex items-center justify-center ">
<FolderStructure data={initialData} addFolder={addFolder} deleteFileorFolder={deleteFileorFolder} />
</div>
</div>
);
};
export default Ffs;
Here is new Trick I learned if you filter(Boolen) it will remove all the null/undefined value.
All Step done.
Want to hire me as a freelancer? Let's discuss.
Drop a message and let's discuss
Drop in your email ID and I will get back to you.