Skip to main content

useControllableState

BetaSince 0.3.0

State your component's parent can control with value and onValueChange, or leave to the component.

import { useControllableState } from "@anantya/uix";

Overview

useControllableState gives a component state that its parent can control with a value and a change handler, or leave to the component with a default. UIX components use it for props such as open, defaultOpen and onOpenChange. In product code, use it in a product component built from UIX components, so it takes the same kind of props.

Building block

UIX components take these props already, so you don't need it to control one.

Missing a component? Ask for it instead of building your own.

Signature

function useControllableState<T>(
options: UseControllableStateOptions<T>,
): [value: T, setValue: Dispatch<SetStateAction<T>>];

interface UseControllableStateOptions<T> {
value?: T;
defaultValue: T;
onValueChange?: (value: T) => void;
}

Parameters

OptionTypeDefaultDescription
valueT-The value, when the parent controls it. undefined leaves it to the hook; null is a value.
defaultValueT-The starting value when the parent doesn't control it. Read on the first render only.
onValueChange(value: T) => void-Called with the new value each time the setter changes it, controlled or not.

Returns

A pair, as useState returns:

  • value: the parent's value while it is set, else the hook's own state
  • setValue: works like useState's setter, with a value or an updater function, and keeps its identity across renders. An updater gets the latest value, also after another change in the same event. It calls onValueChange only when the value changes

While the parent controls the value, a change it doesn't apply is dropped at the next render.

Example

A ReminderSetting product component, built from Switch, keeps its own state in the first row. In the second, the page controls it with enabled and onEnabledChange.

Sent 3 days before each due date.

Customers get no reminders.

Saved for Mehta Textiles: off

Code
"use client";

import { Switch, useControllableState } from "@anantya/uix";
import { useState } from "react";

interface ReminderSettingProps {
enabled?: boolean;
defaultEnabled?: boolean;
onEnabledChange?: (enabled: boolean) => void;
}

function ReminderSetting({
enabled,
defaultEnabled = true,
onEnabledChange,
}: ReminderSettingProps) {
const [on, setOn] = useControllableState({
value: enabled,
defaultValue: defaultEnabled,
onValueChange: onEnabledChange,
});
return (
<div className="flex flex-col gap-1 text-sm">
<label className="flex items-center gap-3 font-medium">
<Switch checked={on} onCheckedChange={setOn} />
Payment reminders
</label>
<p className="text-muted-foreground">
{on ? "Sent 3 days before each due date." : "Customers get no reminders."}
</p>
</div>
);
}

export default function ReminderSettings() {
const [enabled, setEnabled] = useState(false);
return (
<div className="flex flex-col gap-6">
<ReminderSetting />
<div className="flex flex-col gap-2">
<ReminderSetting enabled={enabled} onEnabledChange={setEnabled} />
<p className="text-sm">Saved for Mehta Textiles: {enabled ? "on" : "off"}</p>
</div>
</div>
);
}

Notes

  • Name the props after what they hold, such as enabled, defaultEnabled and onEnabledChange, and map them onto the options, as UIX components do with open, defaultOpen and onOpenChange
  • onValueChange runs in the event that made the change, not in an effect, and not when the parent changes value itself
  • When the parent starts or stops controlling the value, the hook carries on from the current one

Changelog

Every change to useControllableState since 0.3.0 is in the release notes.

© Anantya TechLabs. All rights reserved.