diff --git a/packages/mui-base/src/unstable_useNumberInput/useNumberInput.test.tsx b/packages/mui-base/src/unstable_useNumberInput/useNumberInput.test.tsx index 08c8603dc16c5a..53946ece059bdb 100644 --- a/packages/mui-base/src/unstable_useNumberInput/useNumberInput.test.tsx +++ b/packages/mui-base/src/unstable_useNumberInput/useNumberInput.test.tsx @@ -270,4 +270,42 @@ describe('useNumberInput', () => { expect(handleChange.args[0][1]).to.equal(undefined); }); }); + + describe('warnings', () => { + it('should warn when switching from uncontrolled to controlled', () => { + const handleChange = spy(); + function NumberInput({ value }: { value?: number }) { + const { getInputProps } = useNumberInput({ + onChange: handleChange, + value, + }); + + return ; + } + const { setProps } = render(); + expect(() => { + setProps({ value: 5 }); + }).to.toErrorDev( + 'MUI: A component is changing the uncontrolled value state of NumberInput to be controlled', + ); + }); + + it('should warn when switching from controlled to uncontrolled', () => { + const handleChange = spy(); + function NumberInput({ value }: { value?: number }) { + const { getInputProps } = useNumberInput({ + onChange: handleChange, + value, + }); + + return ; + } + const { setProps } = render(); + expect(() => { + setProps({ value: undefined }); + }).to.toErrorDev( + 'MUI: A component is changing the controlled value state of NumberInput to be uncontrolled', + ); + }); + }); }); diff --git a/packages/mui-base/src/unstable_useNumberInput/useNumberInput.ts b/packages/mui-base/src/unstable_useNumberInput/useNumberInput.ts index dd2646f83025e2..8314a22113433f 100644 --- a/packages/mui-base/src/unstable_useNumberInput/useNumberInput.ts +++ b/packages/mui-base/src/unstable_useNumberInput/useNumberInput.ts @@ -1,7 +1,11 @@ 'use client'; import * as React from 'react'; import MuiError from '@mui/utils/macros/MuiError.macro'; -import { unstable_useForkRef as useForkRef, unstable_useId as useId } from '@mui/utils'; +import { + unstable_useForkRef as useForkRef, + unstable_useId as useId, + unstable_useControlled as useControlled, +} from '@mui/utils'; import { FormControlState, useFormControlContext } from '../FormControl'; import { UseNumberInputParameters, @@ -81,7 +85,12 @@ export function useNumberInput(parameters: UseNumberInputParameters): UseNumberI const [focused, setFocused] = React.useState(false); // the "final" value - const [value, setValue] = React.useState(valueProp ?? defaultValueProp); + const [value, setValue] = useControlled({ + controlled: valueProp, + default: defaultValueProp, + name: 'NumberInput', + }); + // the (potentially) dirty or invalid input value const [dirtyValue, setDirtyValue] = React.useState( value ? String(value) : undefined,