'use client'; import * as React from 'react'; /** * One value that is either controlled by the caller or owned by the component. * * Nine components carried this block by hand — a `useState` for the copy the * component owns, `value !== undefined` for which of the two is live, and a * commit that writes state only while the caller is not controlling it. Thirteen * copies meant thirteen places to keep one rule in step, and the rule had * already drifted: most of them spelled the test `value !== undefined`, while * `Tree` spelled it as the truthiness of the array it was handed. * * Deliberately **not** exported from `src/hooks/index.ts`. That barrel is the * published `/hooks` subpath; this is an implementation detail, and adding * it there would turn an internal refactor into a public API change. * * `onChange` is optional, because the callers split into two groups: * * - one value in, one callback out (`Select`, `Drawer`, `Combobox`, `Command`, * `TreeSelect`, `DataTable`'s sort) — pass `onChange` and the setter reports * every change; * - a callback with a richer signature, or a condition on when it fires * (`Tree`'s `onExpand(keys, info)`, `InputNumber`'s "only when the number * actually moved", `DateRangePicker`'s narrower argument type) — omit * `onChange` and the setter is the state half alone. The caller then invokes * its own callback, which is what it was doing anyway. * * The setter's identity follows `onChange`'s, exactly as the hand-written * `useCallback`s in `Select` and `Drawer` already did — both feed a `useMemo`ed * context, so a setter that changed every render would rebuild it. * * ```tsx * const [open, setOpen] = useControllableState({ * value: controlledOpen, * defaultValue: defaultOpen ?? false, * onChange: onOpenChange, * }); * ``` */ export function useControllableState({ value, defaultValue, onChange, }: { /** The caller's value. `undefined` — and only `undefined` — means "you own it". */ value?: T; /** * Read once, on mount. A function is treated as a lazy initialiser, the same * way `useState` does, which is what lets `Tree` build its `Set` there rather * than on every render. */ defaultValue: T | (() => T); onChange?: (next: T) => void; }): [T, (next: T) => void] { const [uncontrolled, setUncontrolled] = React.useState(defaultValue); const isControlled = value !== undefined; const setValue = React.useCallback( (next: T) => { if (!isControlled) setUncontrolled(next); onChange?.(next); }, [isControlled, onChange] ); return [isControlled ? value : uncontrolled, setValue]; }