use-uncontrolled

Manage state of both controlled and uncontrolled components
Import

Usage

use-uncontrolled allows you to manage state for both controlled and uncontrolled components:

import { useUncontrolled } from '@mantine/hooks';
function CustomInput({ value, defaultValue, onChange }) {
const [_value, handleChange] = useUncontrolled({
value,
defaultValue,
finalValue: 'Final',
rule: (val) => typeof val === 'string',
onChange,
});
return (
<input
type="text"
value={_value}
onChange={(event) => handleChange(event.currentTarget.value)}
/>
);
}

In the example above rule function is used to determine which value should be used initially:

// defaultValue and finalValue are ignored for controlled components
// _value === 'Hello'
<CustomInput value="Hello" defaultValue="There" onChange={() => {}} />
// Since value prop does not meet requirements default value will be used instead
// _value === 'There'
<CustomInput value={null} defaultValue="There" />
// When both value and defaultValue fail to meet requirements, finalValue will be used
// _value === 'Final'
<CustomInput value={null} defaultValue={null} />

Now you can use your component as controlled and uncontrolled input:

// Uncontrolled
<CustomInput />
<CustomInput defaultValue="hello" />
<CustomInput onChange={() => {}} /> // still possible since value is stored in internal state
// Controlled
<CustomInput value="Hello" onChange={() => {}} />

TypeScript

Set value type

By default, hook will set type automatically, but you can provide your own type:

useUncontrolled<number>({
value: 10,
defaultValue: 5,
finalValue: 20,
rule: (val) => val >= 10,
onChange: (val) => console.log(val > 10),
});

Definition

function useUncontrolled<T>(configuration: {
value: T;
defaultValue: T;
finalValue: T;
onChange(value: T): void;
onValueUpdate?(value: T): void;
rule: (value: T) => boolean;
}): readonly [T, (val: T) => void, 'controlled' | 'uncontrolled'];