React Interview Task: Build a folder/file explorer UI.

Rmag Breaking News

Frontend interviews are getting more and more challenging day by day.

Implementing a complex frontend feature/UI live in an interview call is no joke.

I encountered one such situation where I had to build a VS Code like folder/file explorer UI.

This post will discuss the question, thought process, and system design behind it. Finally, we will write code to solve it.

What are we building?

Live preview with code: https://stackblitz.com/edit/vitejs-vite-4bqecn?file=src%2FApp.jsx

Let’s start by understanding the philosophy and system design and then we will start writing code.

Before we begin, I want to mention that this post is part of my weekly newsletter.

Where I build such complex frontend features and solve complex frontend interview questions every week.

If you want to learn with me, join the Newsletter.

Now, let’s continue.

Philosophy

The basic philosophy behind building a “Folder/File explorer UI” is as follows:

List all the folders and files in the root directory.
Clicking on the folder name should expand to show all the files inside that folder.
Clicking on the folder again should collapse to hide all the files inside that folder.
Clicking on a file would do nothing in our case.

CSS will not be the focus of this task.

System Design

We will create a JSON object to mimic a VS code-like folder/file structure. This object will contain all the folders and their nested folders/files.

Alright, but there is a big question here. How do we identify which entry in the JSON object is a folder and which is a file?

Well, we can keep nested folders/files in an array of objects and add the flag isFoloder.

Here is our JSON object with all folders and files.

structure.js

const folderStructureData = {
name: root,
isFolder: true,
items: [
{
name: index.html,
isFolder: false,
},
{
name: app,
isFolder: true,
items: [
{
name: app.js,
isFolder: false,
},
{
name: src,
isFolder: true,
items: [
{
name: main.jsx,
isFolder: false,
},
{
name: utils.js,
isFolder: false,
},
],
},
{
name: app.css,
isFolder: false,
},
],
},
],
};

export default folderStructureData;

Now we need to access this JSON and render the folders/files name in UI. So let’s do that.

Let the coding begin.

We’ll start by fetching JSON data and map over it in App.jsx. And at each iteration, we will check if the item is a folder or a file.

If it is a folder we will return <Folder /> component else <File /> component is returned.

App.jsx

import ./App.css;

import Folder from ./components/Folder;
import File from ./components/File;

import folderStructureData from ./structureData/structure.js;

function App() {
return (
<>
{folderStructureData.items.map((item) => {
return item.isFolder ? <Folder item={item} /> : <File item={item} />;
})}
</>
);
}

export default App;

Now, let’s define our <Folder /> and <File /> components.

components/Folder.jsx

export default function Folder({ item }) {
return (
<>
<p
style={{
background: lightgray,
padding: 2px,
cursor: pointer,
}}
>
&#x276D; {item.name}
</p>
</>
);
}

components/File.jsx

export default function File({ item }) {
return (
<p
style={{
background: lightgray,
padding: 2px,
}}
>
&#9781; {item.name}
</p>
);
}

Awesome, this renders our folders and files on screen. But wait ideally, we should see a folder structure like the following on screen.

—- index.html (file)
—- app (folder)
——– app.js (file)
——– src (folder)
———— main.jsx (file)
———— utils.js (file)
——– app.css (file)

But what we see right now is just index.html and app, but why?

This is where this task becomes a little bit challenging. Can you figure it out on your own? Try and then come back.

Ok, let’s solve it.

So basically, we are iterating over top-level data only in App.jsx that’s why it only shows top-level folder and file names on the screen. How do we iterate over the nested data? Like inside app and then inside src.

Well, we can manually do it for app and src but then it would fail if we change our JSON structure. Our solution should be such that it works automatically with any change in JSON structure.

Did you notice that our top-level and nested data both have the same data structure?

That design will allow us to recursively solve this problem. We will use the concept of recursion to solve this.
Recursion means calling a function within itself.

Ex:
function recurseEx() {
recurseEx();
}

Inside the Folder.jsx we will again map but this time over nested data and check for isFolder flag.

If isFolder is false File.jsx is returned, else Folder.jsx is returned from Folder.jsx itself.

Folder.jsx

import { useState } from react;
import File from ./File;

export default function Folder({ item }) {
const [isExpanded, setIsExpanded] = useState(false);

return (
<>
<p
style={{
background: lightgray,
padding: 2px,
cursor: pointer,
}}
onClick={() => setIsExpanded((prevState) => !prevState)}
>
&#x276D; {item.name}
</p>
<p
style={{
paddingLeft: 12px,
}}
>
{isExpanded &&
item.items.map((item) =>
item.isFolder ? <Folder item={item} /> : <File item={item} />
)}
</p>
</>
);
}

Notice, that the Folder.jsx is returned from Folder.jsx itself, and that is recursion. With that now all our nested data is taken care of, no matter how deeply they are nested.

There is also an isExpanded state added, which just tracks if a folder is in expanded or collapsed state. And based on that the nested data of that folder is shown or hidden.

Now, try changing the JSON structure and our feature will still work. And that is awesome.

P.S:

This feature is not tough to build if you understand recursion. But I also know that recursion is not an easy concept to wrap our heads around.

And there are many more concepts like recursion. That’s why I am building a resource that’ll teach you such programming concepts with visual examples.

And don’t worry it is not gonna cost you anything.

I am still working on that resource and am very close to launching it.

So, stay tuned by joining the Newsletter if you haven’t already.

Thank you,

Comment down on which feature I should build next.

Leave a Reply

Your email address will not be published. Required fields are marked *