mirror of
https://github.com/vercel/components.build.git
synced 2026-09-14 20:06:39 +08:00
89 lines
3.0 KiB
Plaintext
89 lines
3.0 KiB
Plaintext
---
|
|
title: State
|
|
description: How to manage state in a component, as well as merging controllable and uncontrolled state.
|
|
---
|
|
|
|
Building flexible components that work in both controlled and uncontrolled modes is a hallmark of professional components.
|
|
|
|
## Uncontrolled State
|
|
|
|
Uncontrolled state is when the component manages its own state internally. This is the default usage pattern for most components.
|
|
|
|
For example, here's a simple `Stepper` component that manages its own state internally:
|
|
|
|
```tsx title="stepper.tsx"
|
|
import { useState } from 'react';
|
|
|
|
export const Stepper = () => {
|
|
const [value, setValue] = useState(0);
|
|
|
|
return (
|
|
<div>
|
|
<p>{value}</p>
|
|
<button onClick={() => setValue(value + 1)}>Increment</button>
|
|
</div>
|
|
);
|
|
};
|
|
```
|
|
|
|
## Controlled State
|
|
|
|
Controlled state is when the component's state is managed by the parent component. Rather than keeping track of the state internally, we delegate this responsibility to the parent component.
|
|
|
|
Let's rework the `Stepper` component to be controlled by the parent component:
|
|
|
|
```tsx title="stepper.tsx"
|
|
type StepperProps = {
|
|
value: number;
|
|
setValue: (value: number) => void;
|
|
};
|
|
|
|
export const Stepper = ({ value, setValue }: StepperProps) => (
|
|
<div>
|
|
<p>{value}</p>
|
|
<button onClick={() => setValue(value + 1)}>Increment</button>
|
|
</div>
|
|
);
|
|
```
|
|
|
|
## Merging states
|
|
|
|
The best components support both controlled and uncontrolled state. This allows the component to be used in a variety of scenarios, and to be easily customized.
|
|
|
|
[Radix UI](https://www.radix-ui.com/) maintain an internal utility for merging controllable and uncontrolled state called [`use-controllable-state`](https://github.com/radix-ui/primitives/tree/main/packages/react/use-controllable-state). While not intended for public use, registries like [Kibo UI](https://www.kibo-ui.com) have implemented this utility to build their own Radix-like components.
|
|
|
|
Let's install the hook:
|
|
|
|
```package-install
|
|
npm install @radix-ui/react-use-controllable-state
|
|
```
|
|
|
|
This lightweight hook gives you the same state management patterns used internally by Radix UI's component library, ensuring your components behave consistently with industry standards.
|
|
|
|
The hook accepts three main parameters and returns a tuple with the current value and setter. Let's use it to merge the controlled and uncontrolled state of the `Stepper` component:
|
|
|
|
```tsx title="stepper.tsx"
|
|
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
|
|
type StepperProps = {
|
|
value: number;
|
|
defaultValue: number;
|
|
onValueChange: (value: number) => void;
|
|
};
|
|
|
|
export const Stepper = ({ value: controlledValue, defaultValue, onValueChange }: StepperProps) => {
|
|
const [value, setValue] = useControllableState({
|
|
prop: controlledValue, // The controlled value prop
|
|
defaultProp: defaultValue, // Default value for uncontrolled mode
|
|
onChange: onValueChange, // Called when value changes
|
|
});
|
|
|
|
return (
|
|
<div>
|
|
<p>{value}</p>
|
|
<button onClick={() => setValue(value + 1)}>Increment</button>
|
|
</div>
|
|
);
|
|
}
|
|
```
|