File Explorer - Machine Coding Question
Build a file explorer component using React with folder/file creation, navigation, and tree structure management
File Explorer - Machine Coding Question
Problem Statement
Create a file explorer component that allows users to:
- Navigate through a hierarchical file/folder structure
- Create new files and folders
- Expand/collapse folders
- Display files and folders with appropriate icons
- Maintain the tree structure in state
🧠 Understanding the Problem
A file explorer is like the folder structure you see in your computer's file manager. Think of it as a tree where:
- Folders are like branches that can contain other items
- Files are like leaves that don't contain anything else
- You can open/close folders to see what's inside
- You can add new files or folders anywhere in the structure
🏗️ System Design & Architecture
Data Structure
We'll use a tree data structure where each node represents either a file or folder:
{
id: "unique-id",
name: "folder-or-file-name",
isFolder: true/false,
items: [] // only for folders
}Component Architecture
App
└── FileExplorer
└── Folder (recursive component)
├── Folder (for nested folders)
└── File (for files)Step-by-Step Implementation
Step 1: Project Setup
First, let's create our React project:
npx create-react-app file-explorer
cd file-explorerStep 2: Understanding the Core Concepts
What is a Tree Data Structure?
Think of a tree like a family tree:
- Root: The top-most folder (like a grandparent)
- Nodes: Each folder or file (like family members)
- Children: Items inside folders (like children)
- Leaves: Files that don't have children
Why Use Recursion?
Since folders can contain other folders (which can contain more folders), we need a component that can call itself - this is called recursion. It's like a mirror reflecting another mirror!
Step 3: Creating the Data Structure
Let's create our sample data in src/data/folderData.js:
const explorer = {
id: "1",
name: "root",
isFolder: true,
items: [
{
id: "2",
name: "public",
isFolder: true,
items: [
{
id: "3",
name: "public nested 1",
isFolder: true,
items: [
{
id: "4",
name: "index.html",
isFolder: false,
items: []
},
{
id: "5",
name: "hello.html",
isFolder: false,
items: []
}
]
},
{
id: "6",
name: "public_nested_file",
isFolder: false,
items: []
}
]
},
{
id: "7",
name: "src",
isFolder: true,
items: [
{
id: "8",
name: "App.js",
isFolder: false,
items: []
},
{
id: "9",
name: "Index.js",
isFolder: false,
items: []
},
{
id: "10",
name: "styles.css",
isFolder: false,
items: []
}
]
},
{
id: "11",
name: "package.json",
isFolder: false,
items: []
}
]
};
export default explorer;Step 4: Building the Recursive Folder Component
Create src/components/Folder.js:
import React, { useState } from "react";
import "../App.css";
const Folder = ({ explorer, handleInsertNode }) => {
// State for showing input field when creating new items
const [showInput, setShowInput] = useState({
visible: false,
isFolder: null,
});
// State for expanding/collapsing folders
const [expand, setExpand] = useState(false);
// Handle creating new folder or file
const handleNewFolder = (e, isFolder) => {
e.stopPropagation(); // Prevent folder from expanding when clicking button
setExpand(true); // Auto-expand when adding new item
setShowInput({
visible: true,
isFolder,
});
};
// Handle pressing Enter to create new item
const onAddFolder = (e) => {
if (e.keyCode === 13 && e.target.value) {
handleInsertNode(explorer.id, e.target.value, showInput.isFolder);
setShowInput({ ...showInput, visible: false });
}
};
// If it's a folder, render folder structure
if (explorer.isFolder) {
return (
<div style={{ marginTop: 5, marginLeft: 10 }}>
<div className="folder" onClick={() => setExpand(!expand)}>
<span>📂 {explorer.name}</span>
<div>
<button onClick={(e) => handleNewFolder(e, true)}>Folder +</button>
<button onClick={(e) => handleNewFolder(e, false)}>File +</button>
</div>
</div>
{/* Show/hide folder contents */}
<div style={{ display: expand ? "block" : "none", paddingLeft: 25 }}>
{/* Input field for new items */}
{showInput.visible && (
<div className="inputContainer">
<span>{showInput.isFolder ? "📂" : "🗄"}</span>
<input
autoFocus
onKeyDown={onAddFolder}
onBlur={() => setShowInput({ ...showInput, visible: false })}
type="text"
className="inputContainer__input"
/>
</div>
)}
{/* Recursively render child items */}
{explorer.items.map((item) => {
return (
<Folder
handleInsertNode={handleInsertNode}
explorer={item}
key={item.id}
/>
);
})}
</div>
</div>
);
} else {
// If it's a file, render file
return <span className="file">🗄 {explorer.name}</span>;
}
};
export default Folder;Step 5: Creating the Tree Traversal Hook
Create src/hooks/use-traverse-tree.js:
const useTraverseTree = () => {
// Function to insert a new node into the tree
const insertNode = (tree, folderId, item, isFolder) => {
// If we're at the root level
if (tree.id === folderId) {
const newItem = {
id: new Date().getTime(),
name: item,
isFolder: isFolder,
items: []
};
return {
...tree,
items: [...tree.items, newItem]
};
}
// Recursively search through the tree
let latestNode = [];
latestNode = tree.items.map((obj) => {
return insertNode(obj, folderId, item, isFolder);
});
return { ...tree, items: latestNode };
};
return { insertNode };
};
export default useTraverseTree;Step 6: Styling Our Component
Create src/App.css:
.app {
background-color: #ebe1eb;
min-height: 100vh;
padding: 20px;
}
.folder {
margin-top: 6px;
background-color: rgb(194, 185, 185);
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px;
width: 300px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.2s;
}
.folder:hover {
background-color: rgb(180, 170, 170);
}
.inputContainer {
display: flex;
align-items: center;
gap: 5px;
margin: 5px 0;
}
.inputContainer > span {
margin-top: 5px;
}
.inputContainer__input {
margin: 6px 0 0px 0;
padding: 5px;
display: flex;
border: 1px solid lightgray;
align-items: center;
justify-content: space-between;
cursor: pointer;
border-radius: 3px;
}
.folder > span {
margin: 0 5px;
}
.folder > div > button {
font-size: 12px;
background-color: white;
border: 1px solid #ccc;
padding: 4px 8px;
margin-left: 5px;
border-radius: 3px;
cursor: pointer;
}
.folder > div > button:hover {
background-color: #f0f0f0;
}
.file {
margin-top: 5px;
padding-left: 5px;
display: flex;
flex-direction: column;
color: #333;
}Step 7: Bringing It All Together
Update src/App.js:
import React, { useState } from 'react';
import explorer from './data/folderData';
import Folder from './components/Folder';
import './App.css';
import useTraverseTree from './hooks/use-traverse-tree';
const App = () => {
const [explorerData, setExplorerData] = useState(explorer);
const { insertNode } = useTraverseTree();
const handleInsertNode = (folderId, item, isFolder) => {
const finalTree = insertNode(explorerData, folderId, item, isFolder);
setExplorerData(finalTree);
};
return (
<div className='app'>
<h1>📁 File Explorer</h1>
<Folder handleInsertNode={handleInsertNode} explorer={explorerData} />
</div>
);
};
export default App;Key Features Explained
1. Recursive Component Structure
The Folder component calls itself for nested folders. This is like a Russian doll - each doll contains smaller dolls inside!
2. State Management
expand: Controls if a folder is open or closedshowInput: Controls when to show the input field for new itemsexplorerData: Holds the entire tree structure
3. Tree Traversal
The insertNode function recursively searches through the tree to find the correct location to add new items.
4. Event Handling
- Click on folder: Expand/collapse
- Click on buttons: Show input field
- Press Enter: Create new item
- Click outside input: Cancel creation
🧩 Understanding the Code Flow
- Initial Render: App loads with the root folder structure
- User Clicks Folder:
expandstate toggles, showing/hiding contents - User Clicks "+" Button:
showInputbecomes visible with input field - User Types & Presses Enter:
handleInsertNodeis called - Tree Update:
insertNodefinds the correct location and adds the new item - Re-render: Component updates with the new structure
Running the Application
npm startYour file explorer will be running at http://localhost:3000!
What You've Learned
- Tree Data Structures: How to represent hierarchical data
- Recursive Components: Components that can render themselves
- State Management: Managing complex nested state
- Event Handling: Managing user interactions
- CSS Styling: Creating a user-friendly interface
Possible Extensions
- File Operations: Delete, rename, copy files
- Drag & Drop: Move files between folders
- File Preview: Show file contents
- Search: Find files by name
- Breadcrumbs: Show current path
- Keyboard Navigation: Arrow keys to navigate
Tips for Interview
- Start Simple: Begin with just displaying the tree structure
- Add Features Incrementally: Add expand/collapse, then creation, then styling
- Explain Your Approach: Talk through your data structure choice
- Handle Edge Cases: What if the tree is empty? What if there are no permissions?
- Think About Performance: Large trees might need virtualization
🎉 Conclusion
Congratulations! You've built a functional file explorer that demonstrates:
- Complex state management
- Recursive component patterns
- Tree data structure manipulation
- User interaction handling
This is a great foundation for understanding how real file explorers work. The concepts you've learned here apply to many other scenarios like:
- Menu systems
- Organization charts
- Comment threads
- Category hierarchies
Keep exploring and building!
Countdown Timer Component
Master the art of building countdown timers in React using useEffect hook - perfect for quiz apps, games, and time-sensitive applications.
High-Performance Infinite Scroll Component
Master machine coding by creating an infinite scroll component with virtualization, intersection observer, and advanced performance optimization techniques.