Creating a Collapsible Tree View in React

2023-07-24
By: O. Wolfson

Introduction:

In this tutorial, we will learn how to convert a flat data structure into a hierarchical tree view with collapsible nodes using React. This can be useful when displaying nested data, such as a category hierarchy or a folder structure.

Example app deployed at Vercel.

Data Structure:

JavaScript
  // This is your initial flat data structure
  const data: Item[] = [
    { name: "Electronics", id: "1", parent: null },
    { name: "Furniture", id: "2", parent: null },
    { name: "Smartphones", id: "3", parent: "1" },
    { name: "Laptops", id: "4", parent: "1" },
    { name: "Chairs", id: "5", parent: "2" },
    { name: "Gaming Laptops", id: "6", parent: "4" },
    { name: "iPhone", id: "7", parent: "3" },
    { name: "MacBook Pro", id: "8", parent: "4" },
    { name: "Office Chairs", id: "9", parent: "5" },
    { name: "Dining Chairs", id: "10", parent: "5" },
    { name: "Recliners", id: "11", parent: "5" },
    { name: "Gaming Chairs", id: "12", parent: "5" }, // Corrected placement
  ];

Prerequisites:

Basic knowledge of React and JavaScript.

Step 1: Set Up the Project

Create a new React project using Create React App or your preferred method.

Step 2: Define the Data Structure

Define the structure of each item in your data. For example, each item might have a name, unique ID, and a reference to its parent item (if it's a child item).

JavaScript
// Define the structure of an item in your data
type Item = {
  name: string;
  id: string;
  parent: string | null;
};

Step 3: Build the Nested Structure

Create a function that converts the flat data structure into a hierarchical nested structure. We'll call this function buildNestedStructure. It will iterate through the flat data and construct a map of items with their children.

JavaScript
type NestedItem = Item & { children: NestedItem[] };
// This function converts an array of Items into a hierarchical structure of NestedItems
const buildNestedStructure = (items: Item[]): NestedItem[] => {
  const itemMap: { [id: string]: NestedItem } = {};

  // Function to create a NestedItem from an Item
  const createNestedItem = (item: Item): NestedItem => ({
    ...item,
    children: [],
  });

  // Loop through each item in the provided items array
  items.forEach((item) => {
    // For each item, create a new NestedItem by calling the createNestedItem function,
    // and add it to the itemMap object with its ID as the key.
    // This will create a new object that contains all the properties of the original item,
    // plus an empty children array.
    // It's worth noting that at this point, all NestedItems in the itemMap have an empty children array.
    itemMap[item.id] = createNestedItem(item);
    // Next, check if the current item has a parent.
    // If it does, it means it should be nested inside another item.
    if (item.parent) {
      // If the item has a parent, add it to the children array of its parent NestedItem in the itemMap.
      // This is where the hierarchical nesting happens.
      // Note that since we're directly modifying the itemMap objects,
      // the same changes will be reflected in all places where these objects are referenced.
      // So when we added this item to its parent's children array,
      // it also appeared in the children array of the same item in the itemMap.
      itemMap[item.parent].children.push(itemMap[item.id]);
    }
  });

  // Return the array of top-level items, each of which has its children nested within it
  return items.filter((item) => !item.parent).map((item) => itemMap[item.id]);
};

Step 4: Create the Recursive Component

Create a React component to render the nested structure. This component will be recursive, meaning it will render itself for each child item. We'll call this component RenderItem.

JavaScript
// This component takes a NestedItem and a depth, and renders the item and its children
const RenderItem: React.FC<{ item: NestedItem; depth: number }> = ({
  item,
  depth,
}) => {
  const [isExpanded, setIsExpanded] = useState(true);

  const handleToggle = () => {
    setIsExpanded(!isExpanded);
  };

  const paddingStyle = { paddingLeft: `${depth * 20}px` };

  return (
    <div style={paddingStyle}>
      {hasChildren && (
        <button onClick={handleToggle}>
          {isExpanded ? "▼" : "►"} {item.name}
        </button>
      )}
      {isExpanded &&
        item.children.map((child) => (
          <RenderItem key={child.id} item={child} depth={depth + 1} />
        ))}
    </div>
  );
};

Step 5: Implement Collapsible Functionality

Add the ability to collapse and expand nodes in the tree view. We'll use React's useState hook to track the expanded state of each node. When the user clicks on a node with children, it will toggle between expanded and collapsed states.

JavaScript
// Check if the item has children or not
  const hasChildren = item.children.length > 0;

  return (
    <div style={paddingStyle}>
      {hasChildren ? (
        <button onClick={handleToggle}>
          {isExpanded ? "▼" : "►"} {item.name}
        </button>
      ) : (
        <div>{item.name}</div>
      )}
      {isExpanded &&
        item.children.map((child) => (
          <RenderItem key={child.id} item={child} depth={depth + 1} />
        ))}
    </div>
  );

Step 6: Render the Tree View

In the main component of your app, use the buildNestedStructure function to convert your flat data into a nested structure. Then, render the top-level items using the RenderItem component, passing the nested structure as props.

JavaScript
export default function MainComponent() {
  // This is your initial flat data structure
  const data: Item[] = [
    // ... Your data here ...
  ];

  // Build the nested structure from your flat data
  const nestedData = buildNestedStructure(data);

  // Render the top-level items, passing a depth of 0
  return (
    <div>
      {nestedData.map((item) => (
        <RenderItem key={item.id} item={item} depth={0} />
      ))}
    </div>
  );
}

Conclusion:

Congratulations! You've successfully created a collapsible tree view from a flat data structure using React. This allows you to display nested data in a user-friendly and organized way, with the ability to expand and collapse nodes as needed.

Here is the full code for reference.

Extra Tips:

  • You can further enhance the tree view by adding animations to the expanding and collapsing of nodes.
  • Consider using icons or different styles to visually distinguish between parent and child nodes.
  • Experiment with different ways of structuring your data to best fit your specific use case.

This tutorial provides a basic example of how to create a collapsible tree view in React. Depending on your project's complexity and requirements, you may need to adjust and customize the implementation accordingly.