---
# System prepended metadata

title: Recursive Components in React

---

###### tags: `React` `recursive`

# Recursive Components in React

![recursive_file_explorer](https://hackmd.io/_uploads/rJhROBWca.gif)

## When think about recursive...
[Recursion MDN](https://developer.mozilla.org/en-US/docs/Glossary/Recursion)

每次想到遞迴，就會聯想到費式數列的題目。
- **factorial function**
The factorial function (symbol: !) says to multiply all whole numbers from our chosen number down to 1.
Examples:
4! = 4 × 3 × 2 × 1 = 24
7! = 7 × 6 × 5 × 4 × 3 × 2 × 1 = 5040
1! = 1
```javascript
function factorial(num) {
    if (num <= 1) { // base condition
        return 1;
    }
    return num * factorial(num - 1); // function calling itself with new input value.
}
console.log(factorial(6)); // 720 
```
遞迴不只適用於一般函式的應用，還可以用在像 Recursive Component 這樣的 UI 呈現。

## What is Recursive Component
Recursive Component 是一種設計方法，特色在於使用相似或相同的元素或 component 嵌套自身，以建立具有動態且層次結構的介面。

## When to use
- Nested file explorer，例如樹狀結構的檔案總管
- 其他階層式或樹狀結構的使用者介面設計

## Recursion v.s. Loops
- Recursion 呼叫一個呼叫自身的函數，而 loops 需要不斷呼叫相同的代碼直到滿足特定條件。
- 變數聲明：
在 loops 中，必須在迭代之前聲明控制變數（例如，計數器 let i=0）。使用 Recursion 時，不需要聲明變數來執行操作。
- 增加/減少：
迴圈可能需要增加或減少控制變數以避免無窮迴圈。
遞迴不依賴於變數，只需要一個基本條件來停止函數呼叫。
- 返回值：
迴圈並不直接返回值。
遞迴函數在每次調用時都需要返回值，以便在遞迴的下一步中使用。
- 可讀性和模組化：
在樹狀結構情境中，遞迴使代碼更加可讀和模組化。使用迴圈可能使代碼變得更長。

## How to implement

### 實例情境
下面的資料結構中，包含一個物件陣列，每個物件都擁有一個稱為 children 的鍵（key），該鍵對應到另一個物件陣列。同時，這些物件的 children 鍵再度對應到更多的物件陣列。在這種情況下，適合使用 Recursive Component。
```javascript
{
  "name": "MyReactApp",
  "type": "folder",
  "children": [
    {
      "name": "src",
      "type": "folder",
      "children": [
        {
          "name": "components",
          "type": "folder",
          "children": [
            {
              "name": "Header.js",
              "type": "file"
            },
            {
              "name": "Footer.js",
              "type": "file"
            }
          ]
        },
        {
          "name": "pages",
          "type": "folder",
          "children": [
            {
              "name": "Home.js",
              "type": "file"
            },
            {
              "name": "About.js",
              "type": "file"
            }
          ]
        },
        {
          "name": "App.js",
          "type": "file"
        },
        {
          "name": "index.js",
          "type": "file"
        }
      ]
    },
    {
      "name": "public",
      "type": "folder",
      "children": [
        {
          "name": "index.html",
          "type": "file"
        },
        {
          "name": "favicon.ico",
          "type": "file"
        }
      ]
    },
    {
      "name": "node_modules",
      "type": "folder",
      "children": []
    },
    {
      "name": "package.json",
      "type": "file"
    },
    {
      "name": "package-lock.json",
      "type": "file"
    }
  ]
}

```
### 程式碼範例
[在 CodeSandbox 上查看程式碼範例](https://codesandbox.io/p/sandbox/recursive-folder-cvwy5n?file=%2Fsrc%2FApp.js)

1. 透過檢查 files.children 是否存在判斷該 item 是否有子元素。如果存在，代表是一個資料夾，並會以相同的 Directory 元件遞迴呼叫，進一步呈現下一層的子檔案或子資料夾。如果 files.children 不存在，則代表是一個檔案。
3. isExpand 決定 folder files 的 show / hide
```javascript=
import { useState, useRef, useEffect } from "react";

const Directory = ({ files }) => {
  const [isExpanded, setIsExpanded] = useState(true);
  const [contentHeight, setContentHeight] = useState(null);
  const contentRef = useRef(null);

  const handleClickFolder = () => {
    setIsExpanded((prev) => !prev);
  };

  useEffect(() => {
    setContentHeight(isExpanded ? contentRef.current?.scrollHeight : 0);
  }, [isExpanded]);

  const getMaxHeight = () => {
    return isExpanded ? contentHeight : 0;
  };

  if (files.children) {
    return (
      <div className="folder">
        <div className="folder-title" onClick={handleClickFolder}>
          <div className={`arrow-icon ${isExpanded ? "expand" : ""}`}></div>
          <div>{files.name}</div>
        </div>
        <div
          className={`folder-content ${isExpanded ? "show" : ""}`}
          style={{ height: getMaxHeight() }}
          ref={contentRef}
        >
          {isExpanded &&
            files.children.map((file) => (
              <Directory key={file.name} files={file} />
            ))}
        </div>
      </div>
    );
  }
  return <div className="file">{files.name}</div>;
};

export default Directory;

```

## Additional: Recursive Components in Vue
[Vue recursive components: Rendering nested comments](https://blog.logrocket.com/rendering-nested-comments-recursive-components-vue/)

## Reference
- [Recursive components in React: A real-world example](https://dev.to/logrocket/recursive-components-in-react-a-real-world-example-328g?fbclid=IwAR3Kz6UrhR_bEJCZiHbatZVnHqIU9jNOe6mB4JnzFJtZEuC-TLIZxWz9BLE)
- [How to Use Recursion in React](https://www.freecodecamp.org/news/how-to-use-recursion-in-react/)
- [Creating a simple file explorer with recursive components in React](https://dev.to/siddharthvenkatesh/creating-a-simple-file-explorer-with-recursive-components-in-react-458h)