Files
2026-08-07 21:39:30 +02:00

66 lines
2.8 KiB
JavaScript

'use client';
// TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- process.env never changes, dependency arrays are intentionally ignored
/* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */
import * as React from 'react';
import { error } from "./error.mjs";
export function useControlled({
controlled,
default: defaultProp,
name,
state = 'value'
}) {
// isControlled is ignored in the hook dependency lists as it should never change.
const {
current: isControlled
} = React.useRef(controlled !== undefined);
const [valueState, setValue] = React.useState(defaultProp);
const value = isControlled ? controlled : valueState;
if (process.env.NODE_ENV !== 'production') {
React.useEffect(() => {
if (isControlled !== (controlled !== undefined)) {
error([`A component is changing the ${isControlled ? '' : 'un'}controlled ${state} state of ${name} to be ${isControlled ? 'un' : ''}controlled.`, 'Elements should not switch from uncontrolled to controlled (or vice versa).', `Decide between using a controlled or uncontrolled ${name} ` + 'element for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n'));
}
}, [state, name, controlled]);
const {
current: defaultValue
} = React.useRef(defaultProp);
React.useEffect(() => {
if (!isControlled && serializeToDevModeString(defaultValue) !== serializeToDevModeString(defaultProp)) {
error([`A component is changing the default ${state} state of an uncontrolled ${name} after being initialized. ` + `To suppress this warning opt to use a controlled ${name}.`].join('\n'));
}
}, [defaultProp]);
}
const setValueIfUncontrolled = React.useCallback(newValue => {
if (!isControlled) {
setValue(newValue);
}
}, []);
return [value, setValueIfUncontrolled];
}
function serializeToDevModeString(input) {
let nextId = 0;
const seen = new WeakMap();
try {
const result = JSON.stringify(input, function replacer(key, value) {
if (key === '_owner' && this != null && typeof this === 'object' && '$$typeof' in this) {
return undefined;
}
if (typeof value === 'bigint') {
return `__bigint__:${value}`;
}
if (value !== null && typeof value === 'object') {
const id = seen.get(value);
if (id !== undefined) {
return `__object__:${id}`;
}
seen.set(value, nextId);
nextId += 1;
}
return value;
});
return result ?? `__top__:${typeof input}`;
} catch {
return '__unserializable__';
}
}