Use Controllable State
Use Controllable State from Aur UI.
bash
pnpm add @aur-ui/uitsx
import { useControllableState } from "@aur-ui/ui/hooks/use-controllable-state";hooks/use-controllable-state.ts
"use client";
import { type SetStateAction, useCallback, useRef, useState } from "react";
/**
* Minimal controlled/uncontrolled state hook. Lets a component accept either
* `value` (controlled) or fall back to `defaultValue` (uncontrolled), always
* firing `onChange` on updates.
*
* `setValue` accepts a plain value or a functional updater. Updaters resolve
* against the latest value (tracked in a ref), so rapid successive updates in
* one tick never read a stale closure — in either mode.
*
* Naming convention for consuming components: expose `value` / `defaultValue`
* / `onValueChange`.
*/
export function useControllableState<T>(params: {
value: T | undefined;
defaultValue: T;
onChange?: (value: T) => void;
}): [T, (next: SetStateAction<T>) => void] {
const { value, defaultValue, onChange } = params;
const isControlled = value !== undefined;
const [uncontrolled, setUncontrolled] = useState(defaultValue);
// Keep the latest onChange without forcing setValue to change identity.
const onChangeRef = useRef(onChange);
onChangeRef.current = onChange;
const stateValue = isControlled ? value : uncontrolled;
// Latest resolved value, for stale-closure-safe functional updaters.
const valueRef = useRef(stateValue);
valueRef.current = stateValue;
const setValue = useCallback(
(next: SetStateAction<T>) => {
const resolved =
typeof next === "function"
? (next as (prev: T) => T)(valueRef.current)
: next;
valueRef.current = resolved;
if (!isControlled) setUncontrolled(resolved);
onChangeRef.current?.(resolved);
},
[isControlled],
);
return [stateValue, setValue];
}