esc
navigate select
Browse documentation

Controlled State

Pass expandedIds / checkedIds / selectedIds explicitly whenever another part of your app needs to read or drive tree state.

This is the same controlled-component pattern React uses for form inputs: you own the state, TreeKit renders it and calls your onChange handler when the user interacts — it never updates your state for you. Reach for controlled state when you need to:

  • Persist selection to a URL, form, or backend.
  • Drive the tree from buttons elsewhere on the page ("Expand all", "Clear selection").
  • Keep two trees, or a tree and a summary panel, in sync.
  • Validate or transform a selection before it's applied.
Engineering
Show code
const [expandedIds, setExpandedIds] = useState<string[]>(["engineering"]);
const [checkedIds, setCheckedIds] = useState<string[]>([]);

<TreeKit
  data={data}
  expandedIds={expandedIds}
  onExpandedChange={setExpandedIds}
  checkedIds={checkedIds}
  onCheckedChange={setCheckedIds}
/>

<button onClick={() => setCheckedIds(allIds)}>Check all</button>

Controlled means fully controlled

Once you pass checkedIds, TreeKit will not check/uncheck anything on its own — every click calls onCheckedChange with the id set TreeKit computed, and it's up to you to call your setter. Forgetting to (e.g. passing onCheckedChange={() => {}}) will make checkboxes appear unresponsive — this is standard controlled-input behavior, not a bug.

You can control any subset independently — e.g. controlled checkedIds with uncontrolled expansion (defaultExpandedIds only). See Uncontrolled State for the other end of this spectrum.