in packages/react/src/hooks/useControllable.ts [78:133]
export function useControllable<ValueType>({
controlledValue,
handler,
defaultValue,
propertyDescription: { componentName, changeHandler, controlledProp },
}: UseControllableProps<ValueType>) {
// The decision whether a component is controlled or uncontrolled is made on its first render and cannot be changed afterwards.
const isControlled = React.useState(controlledValue !== undefined)[0];
if (isDevelopment()) {
// Print a warning if the component switches between controlled and uncontrolled mode.
React.useEffect(() => {
if (isControlled && handler === undefined) {
console.warn(
`${componentName}: You provided a \`${controlledProp}\` prop without an \`${changeHandler}\` handler. This will render a non-interactive component.`
);
}
}, [handler, isControlled, componentName, changeHandler, controlledProp]);
React.useEffect(() => {
const isControlledNow = controlledValue !== undefined;
if (isControlled !== isControlledNow) {
const initialMode = isControlled ? 'controlled' : 'uncontrolled';
const modeNow = isControlledNow ? 'controlled' : 'uncontrolled';
console.warn(
`${componentName}: A component tried to change ${initialMode} '${controlledProp}' property to be ${modeNow}. ` +
`This is not supported. Properties should not switch from ${initialMode} to ${modeNow} (or vice versa). ` +
`Decide between using a controlled or uncontrolled mode for the lifetime of the component. ` +
`More info: https://fb.me/react-controlled-components`
);
}
}, [isControlled, controlledProp, componentName, controlledValue]);
}
// This is the value that is used if the component is uncontrolled.
const [valueState, setValue] = React.useState(defaultValue);
const [valueHasBeenSet, setValueHasBeenSet] = React.useState(false);
// We track changes to the defaultValue
const currentUncontrolledValue = valueHasBeenSet ? valueState : defaultValue;
const setUncontrolledValue = React.useCallback(
(newValue: React.SetStateAction<ValueType>) => {
setValue(newValue);
setValueHasBeenSet(true);
},
[setValue, setValueHasBeenSet]
);
if (isControlled) {
return [controlledValue, defaultCallback] as const;
} else {
return [currentUncontrolledValue, setUncontrolledValue] as const;
}
}