useControlledValue
useControlledValue(controlled, defaultValue, onChange) is useControlledState with the change callback built in. It returns [value, change]. change(next, ...args) does nothing when next equals the value from the last render (Object.is), so two calls with the same new value in one event both call onChange. Otherwise it stores next when uncontrolled, then calls onChange(next, ...args), controlled or not.
function Filter({ value, defaultValue = 'all', onValueChange }: FilterProps) {
const [current, change] = useControlledValue(value, defaultValue, onValueChange)
return (
<select value={current} onChange={(event) => change(event.currentTarget.value)}>
<option value="all">All</option>
<option value="open">Open</option>
</select>
)
}
Extra arguments, such as a reason, pass through to onChange: type them as the second type parameter, as in useControlledValue<boolean, [reason: string]>(…). As with useControlledState, undefined means uncontrolled and change takes a value, not an updater function.
When to use
When: A component of your own with a value prop, a default and a change callback that should fire only when the value changes.
When not: You call the change callback yourself, or need a setter with no callback → useControlledState.
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| controlled | T | undefined | yes | — | The value prop. undefined means uncontrolled, so use null for an empty controlled value. |
| defaultValue | T | yes | — | Starting value when uncontrolled. Read on the first render only. |
| onChange | (next: T, ...args: A) => void | no | — | Called with each new value and any extra arguments, such as a reason, controlled or not. |