Why This Question Exists
Tree Navigation I is a pure recursive component challenge — the simplest possible version. The interviewer is evaluating whether you naturally model recursive data with recursive components, and whether you centralise UI state at the correct level. Candidates who put open/selected state inside each TreeNode fail the "expand all" button test — you can't control per-node state from outside if it's internal to each node.
Recursive Component Pattern
function TreeNode({ node, depth = 0, open, toggle, selected, select }) {
const isFolder = Array.isArray(node.children);
if (!isFolder) {
// Base case: leaf node — render a selectable file
return (
<div style={{ marginLeft: depth * 16 }} onClick={() => select(node.id)}>
📄 {node.name}
</div>
);
}
// Recursive case: render folder + its children
const isOpen = open.has(node.id);
return (
<div>
<div style={{ marginLeft: depth * 16 }} onClick={() => toggle(node.id)}>
{isOpen ? '📂' : '📁'} {node.name}
</div>
{isOpen && node.children.map(child => (
<TreeNode key={child.id} node={child} depth={depth + 1}
open={open} toggle={toggle} selected={selected} select={select} />
))}
</div>
);
}
ℹ Interview TipSay: "I keep the open Set and selected ID in the root parent component. This makes the tree fully controlled — any external code can programmatically expand, collapse, or select nodes by updating those values. If I put the state inside each TreeNode, I'd lose that control."
Centralised State Design
// In the root App component — NOT inside TreeNode
const [open, setOpen] = useState(new Set(['root'])); // root expanded by default
const [selected, setSelected] = useState(null);
const toggle = (id) => setOpen(prev => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
return next; // new Set — React detects the change
});
// Pass all state down as props — TreeNode has zero internal state
With centralised state, an "Expand All" button is just setOpen(new Set(allFolderIds)). A "Collapse All" button is setOpen(new Set()). These are trivial to implement because state lives at the top.
Folder vs File Detection
// Option 1: type field in data
const isFolder = node.type === 'folder';
// Option 2: presence of children array (used here)
const isFolder = Array.isArray(node.children);
// children: [] → empty folder (still a folder!)
// children: undefined → file
Using the presence of a children array is the most common approach. An empty array is a valid empty folder (a folder that happens to have no files yet). Undefined/null means it's a file.
⚠ Common Pitfall: Infinite recursion with circular dataIf the tree data has circular references (e.g., a child references its own parent), a recursive component will infinite-loop. Always validate tree data before rendering. In interviews, it's enough to mention this and say you'd validate the input data structure at the API layer.