April 29, 2020     8min read

Building A React Folder Tree Component

Hey folks! I hope you are doing fine in this pandemic situation, Today we will be creating a Folder Tree Component in Reactjs from scratch.

Designing the API

Before creating any type of component in reactjs you should 1st design how the API would look like and then write the neccessary code to make it work.

Our Folder Tree Component will have two APIs

  • Declarative
  • Imperative

At first we will tackle the Declarative API which is really simple to create,
and in the second section we will do the Imperative API with recursive components.

Declarative API

The declarative API would look something like this :-

import Tree from './Tree';

const App = () => {
  return (
    <div>
      <Tree>
        <Tree.Folder name="src">
          <Tree.Folder name="Components">
            <Tree.File name="Modal.js" />
            <Tree.File name="Modal.css" />
          </Tree.Folder>
          <Tree.File name="index.js" />
          <Tree.File name="index.html" />
        </Tree.Folder>
        <Tree.File name="package.json" />
      </Tree>
    </div>
  );
};

As you can see we will have total of three components to work with

  • <Tree /> (the root)
  • <Tree.Folder /> (it will be collapsible)
  • <Tree.File />

Imperative API

Declarative APIs are simple & easier for users to structure the Tree, but in real world scenarios we will have a JSON Object representing the Folder Tree and we need to render that with the Imperative API.

import Tree from './Tree';

const structure = [
  {
    type: "folder",
    name: "src",
    childrens: [
      {
        type: "folder",
        name: "Components",
        childrens: [
          { type: "file", name: "Modal.js" },
          { type: "file", name: "Modal.css" }
        ]
      },
      { type: "file", name: "index.js" },
      { type: "file", name: "index.html" }
    ]
  },
  { type: "file", name: "package.json" }
];

const App = () => {
  return (
    <div>
      <Tree data={structure} />
    </div>
  );
};

As you can see we have a JSON representation of the same tree we have in our declarative API.
Its an Array of Objects and each object has three properties

  • name
  • type (defines if its a Folder or File)
  • childrens (array of nested Files & Folders)

And we just passed this structure to our <Tree /> component which will handle the rendering, we will cover the Imperative API later in the post but let us first finish our Declerative API


The Tree Component

Lets first install styled-components for styling our Components.

npm install styled-components

Our Tree component will be very simple it will only have some basic styling, it is so simple i don't even have to explain it.

const StyledTree = styled.div`
  line-height: 1.5;
`;

const Tree = ({ children }) => {
  return <StyledTree>{children}</StyledTree>;
};

The File Component

In our File component we will also have a File Icon with some basic styling.

Lets install react-icons and import our File Icon

npm install react-icons
import { AiOutlineFile } from 'react-icons/ai';

const StyledFile = styled.div`
  padding-left: 20px;
  display: flex;
  align-items: center;
  span {
    margin-left: 5px;
  }
`;

const File = ({ name }) => {
  return (
    <StyledFile>
      <AiOutlineFile />
      <span>{name}</span>
    </StyledFile>
  );
};

We have a 20px padding left to push the Component to the right a little bit, and display flex properties to align the icon & span correctly.

Thats fine but that generic file icon does not look good, does it? lets change that.
We will make a map of extension icons and depending on the file extension we will give the file appropriate icon.

import { DiJavascript1, DiCss3Full, DiHtml5, DiReact } from 'react-icons/di';

const FILE_ICONS = {
  js: <DiJavascript1 />,
  css: <DiCss3Full />,
  html: <DiHtml5 />,
  jsx: <DiReact />,
};
export default FILE_ICONS;

And in the File component we will extract the extension from the name of the file and use that to render the icon

import FILE_ICONS from './FileIcons';

const File = ({ name }) => {
  // get the extension
  let ext = name.split('.')[1];

  return (
    <StyledFile>
      {/* render the extension or fallback to generic file icon  */}
      {FILE_ICONS[ext] || <AiOutlineFile />}
      <span>{name}</span>
    </StyledFile>
  );
};

It would look something like this.
Hooray our File component is done, let's move on to Folder component

File Component With Icons
File Component With Icons

The Folder Component

In the Folder component we will have

  • folder title
  • collapsible component
  • Nested childrens of File/Folder components

Initially our Folder component is very simple, just the title and childrens.

btw if you dont know what that children prop is then heres some resources
A quick intro to React’s props.children
A deep dive into children in React

import { AiOutlineFolder } from 'react-icons/ai';

const StyledFolder = styled.div`
  padding-left: 20px;

  .folder--label {
    display: flex;
    align-items: center;
    span {
      margin-left: 5px;
    }
  }
`;

const Folder = ({ name, children }) => {
  return (
    <StyledFolder>
      <div className="folder--label">
        <AiOutlineFolder />
        <span>{name}</span>
      </div>
      <div>{children}</div>
    </StyledFolder>
  );
};

And thats it, thats all we need for our Folder component but we also wanted the folders to be collapsible so lets add that next.

To add the collapse feature we will add a <Collapse /> Styled component and also add local state to keep track of isOpen state of our component.

const StyledFolder = styled.div`
  padding-left: 20px;

  .folder--label {
    display: flex;
    align-items: center;
    span {
      margin-left: 5px;
    }
  }
`;
const Collapsible = styled.div`
  /* set the height depending on isOpen prop */
  height: ${p => (p.isOpen ? 'auto' : '0')};
  /* hide the excess content */
  overflow: hidden;
`;

const Folder = ({ name, children }) => {
  const [isOpen, setIsOpen] = useState(false);

  const handleToggle = e => {
    e.preventDefault();
    setIsOpen(!isOpen);
  };

  return (
    <StyledFolder>
      <div className="folder--label" onClick={handleToggle}>
        <AiOutlineFolder />
        <span>{name}</span>
      </div>
      <Collapsible isOpen={isOpen}>{children}</Collapsible>
    </StyledFolder>
  );
};

There we go! Our Folder Component is done, Yey!

Finalizing Tree Component

As you've noticed in our Declerative API design, we have <Tree.File /> & <Tree.Folder /> components we can just assign the File & Folder component to our Tree's static methods.

const Tree = ({ children }) => {
  return <StyledTree>{children}</StyledTree>;
};

Tree.File = File;
Tree.Folder = Folder;

////////
// DONE! Lets try it out
import Tree from './Tree';

const App = () => {
  return (
    <div>
      <Tree>
        <Tree.Folder name="src">
          <Tree.Folder name="Components">
            <Tree.File name="Modal.js" />
            <Tree.File name="Modal.css" />
          </Tree.Folder>
          <Tree.File name="index.js" />
          <Tree.File name="index.html" />
        </Tree.Folder>
        <Tree.File name="package.json" />
      </Tree>
    </div>
  );
};

If we run the code now we will have a working React Folder Tree Component! Congrats 🎉🎉

Declerative Demo


Imperative API

OKAY! we are done with the Declerative API now lets work on the Imperative API.

To create the Imperative API we need recursion!

If you are not familiar with recursion then check out some resources
A Quick Intro to Recursion in Javascript
Intro to Recursion in JS

In our <Tree /> component we accept the data props and added an isImperative flag.
If we have the data prop and not the children that means the user is using the imperative api, and depending on that variable we will be rendering the tree.

const Tree = ({ data, children }) => {
  const isImperative = data && !children;

  return <StyledTree>{isImperative ? <TreeRecursive data={data} /> : children}</StyledTree>;
};

As you've noticed we also added a new component called <TreeRecursive /> which will recursively look through the JSON structure and render those nested files/folders, Lets implement it.

const TreeRecursive = ({ data }) => {
  // loop through the data
  return data.map(item => {
    // if its a file render <File />
    if (item.type === 'file') {
      return <File name={item.name} />;
    }
    // if its a folder render <Folder />
    if (item.type === 'folder') {
      return (
        <Folder name={item.name}>
          {/* Call the <TreeRecursive /> component with the current item.childrens */}
          <TreeRecursive data={item.childrens} />
        </Folder>
      );
    }
  });
};

Believe it or not, we are DONE! 🎉🎉 Run the code and see the magic!

Imperative Demo

Phew! That was amazing isn't it? if you made it this far, give yourself a tap on the shoulder because you just built a React Folder Tree Component!

Now for those who are looking for some more fun try to implement these features on your own :-

  • File/Folder Rename Support
  • File/Folder Creation Support
  • File/Folder Deletion Support
  • Save folder structure to localStorage Support

Foot notes

Thanks for reading the post, i hope you learned something and enjoyed it.
Stay safe, stay home!
Bye have a nice day!