Build a file tree with lazy loading
Build a file tree. Three root nodes are provided (`src/`, `tests/`, `docs/`), each a folder.
Clicking a folder toggles it open/closed. On first open, its children are loaded (simulated with a short setTimeout) and cached — subsequent opens don't re-fetch.
The tree component is itself recursive — a folder renders its children by rendering more of itself.
Examples
// Initial: three collapsed folders — src/, tests/, docs/
// Click toggle-src
// → <span data-testid="loading-src">Loading…</span>
// → (150ms later)
// → <ul>
// <li data-testid="node-src.App.js">App.js</li>
// <li data-testid="node-src.components">…</li>
// </ul>
// Collapse, re-open → children appear instantly, no loading.First open loads + caches; subsequent opens render from cache.
function TreeNode({ node, path }) {
const [isOpen, setIsOpen] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
const [isLoading, setIsLoading] = useState(false);
// ...
return (
<li data-testid={`node-${path}`}>
<button onClick={toggle}>{node.name}</button>
{isOpen && (isLoading
? <span data-testid={`loading-${path}`}>Loading…</span>
: <ul>{node.children.map((c) => <TreeNode node={c} />)}</ul>
)}
</li>
);
}Recursive component: each folder renders a `<ul>` of `TreeNode` for its children.
What it has to do
- Each node is a `<li data-testid="node-{path}">`, where `{path}` is the dot-separated path from the root (e.g. `src.components.Button.tsx`).
- Each folder's label is a `<button data-testid="toggle-{path}">` showing the folder name.
- Clicking a folder toggles its open state.
- On first open, show `<span data-testid="loading-{path}">Loading…</span>` while children fetch; replace with the children `<ul>` once loaded. Second open does not re-show the loading state.
- A collapsed folder's children are not present in the DOM at all (unmounted, not just hidden).
- Each file (leaf) renders its name as plain text — files are not clickable toggles.
Notes
- Keep `isOpen` and `isLoaded` as separate state per node — collapsing doesn't throw away the cache, so re-open is instant.
- Collapsed children should be unmounted, not CSS-hidden — otherwise a very deep tree builds the entire DOM upfront and defeats lazy loading.
- In production, swap the local `setTimeout` for a real fetch keyed on the folder path, and show a retry affordance if the request fails. The loading placeholder is the same.
- Accessibility: expose each folder as `role="treeitem"` inside `role="tree"`, with `aria-expanded` and `aria-level`; arrow keys should move focus between siblings/parents.
Stuck? The react course covers the ideas this problem is built on.
import { useState } from "react"; const ROOT = [ { name: "src", type: "folder", children: [ { name: "App.js", type: "file" }, { name: "components", type: "folder", children: [ { name: "Button.tsx", type: "file" }, { name: "Modal.tsx", type: "file" }, ], }, ], }, { name: "tests", type: "folder", children: [{ name: "App.test.js", type: "file" }] }, { name: "docs", type: "folder", children: [{ name: "README.md", type: "file" }] }, ]; function TreeNode({ node, path }) { // TODO: for a folder, toggle open on click, show "Loading…" on first open // (for ~150ms via setTimeout), then render children. For a file, render the // name. Both are wrapped in <li data-testid={`node-${path}`}>. return ( <li data-testid={`node-${path}`}> {node.name} </li> ); } export default function App() { return ( <ul style={{ fontFamily: "sans-serif", padding: 16, listStyle: "none" }}> {ROOT.map((n) => ( <TreeNode key={n.name} node={n} path={n.name} /> ))} </ul> ); }
- 1.Each node is a `<li data-testid="node-{path}">`, where `{path}` is the dot-separated path from the root (e.g. `src.components.Button.tsx`).
- 2.Each folder's label is a `<button data-testid="toggle-{path}">` showing the folder name.
- 3.Clicking a folder toggles its open state.
- 4.On first open, show `<span data-testid="loading-{path}">Loading…</span>` while children fetch; replace with the children `<ul>` once loaded. Second open does not re-show the loading state.
- 5.A collapsed folder's children are not present in the DOM at all (unmounted, not just hidden).
- 6.Each file (leaf) renders its name as plain text — files are not clickable toggles.
5 automated assertions run against your rendered component. The exact assertion names are hidden so you build to the behaviour, not to the test.
Discussion
Sign in to postNo comments yet. Be the first to say something.