Skip to content

Commit 1e8c35d

Browse files
mj12albertmapache-salvaje
authored andcommitted
[useNumberInput][base-ui] Fix change handlers passed through slotProps (mui#39407)
Signed-off-by: Albert Yu <[email protected]> Co-authored-by: Sam Sycamore <[email protected]>
1 parent fed2d0c commit 1e8c35d

File tree

6 files changed

+430
-198
lines changed

6 files changed

+430
-198
lines changed

docs/data/base/components/number-input/number-input.md

+82-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Here's an example of a custom component built using the `useNumberInput` hook wi
114114
{{"demo": "UseNumberInput.js", "defaultCodeOpen": false}}
115115

116116
Here's an example of a "compact" number input component using the hook that only consists of the stepper buttons.
117-
In this demo, `onChange` is used to write the latest value of the component to a state variable.
117+
In this demo, [`onChange`](#events) is used to write the latest value of the component to a state variable.
118118

119119
{{"demo": "UseNumberInputCompact.js", "defaultCodeOpen": false}}
120120

@@ -169,6 +169,87 @@ In the following snippet, if <kbd class="key">Shift</kbd> is held when clicking
169169
<NumberInput min={0} step={1} shiftMultiplier={5} />
170170
```
171171

172+
### Events
173+
174+
The Number Input component and hook provide two props–`onChange` and `onInputChange`–that accept event handlers for when the value of the component changes.
175+
176+
#### onChange
177+
178+
`onChange` accepts a custom event handler that is called with two arguments: the underlying event, and the latest "clamped" value.
179+
180+
```ts
181+
onChange: (
182+
event: React.FocusEvent<HTMLInputElement> | React.PointerEvent | React.KeyboardEvent,
183+
value: number | undefined,
184+
) => void;
185+
```
186+
187+
It's called when the `<input>` element is blurred, or when the stepper buttons are clicked, after the value has been clamped based on the [`min`, `max`](#minimum-and-maximum), or [`step`](#incremental-steps) props.
188+
189+
:::info
190+
When using the component, `onChange` can only be passed as a prop on the component—not through `slotProps`.
191+
:::
192+
193+
```jsx
194+
// ✅ Works
195+
<NumberInput
196+
onChange={(event, newValue) => console.log(`${event.type} event: the new value is ${newValue}`)}
197+
/>
198+
199+
// ❌ Doesn't work
200+
<NumberInput
201+
slotProps={{
202+
input: {
203+
// expects a native input change event handler, newValue is always undefined
204+
onChange: (event, newValue) => { ... },
205+
},
206+
}}
207+
/>
208+
```
209+
210+
#### onInputChange
211+
212+
`onInputChange` accepts a native input change handler that's passed to the `<input>` element:
213+
214+
```ts
215+
onInputChange: React.ChangeEventHandler<HTMLInputElement>;
216+
```
217+
218+
It's called whenever the value of the textbox changes–for example, on every keystroke typed into it, before clamping is applied.
219+
220+
In other words, it's possible for `event.target.value` to contain out-of-range values or non-numerical characters.
221+
222+
:::info
223+
When using the component, `onInputChange` can only be passed as a prop on the component. If you prefer to use `slotProps`, pass it as `slotProps.input.onChange` instead. Both ways–`onInputChange` and `slotProps.input.onChange`–work the same.
224+
:::
225+
226+
```jsx
227+
// ✅ Works
228+
<NumberInput
229+
onInputChange={(event) => console.log(`the input value is: ${event.target.value}`)}
230+
/>
231+
232+
// ✅ Works
233+
<NumberInput
234+
slotProps={{
235+
input: {
236+
// this exactly the same as onInputChange above
237+
onChange: (event) => console.log(`the input value is: ${event.target.value}`),
238+
},
239+
}}
240+
/>
241+
242+
// ❌ Doesn't work
243+
<NumberInput
244+
slotProps={{
245+
input: {
246+
// This will throw "unknown event handler"
247+
onInputChange: () => {},
248+
},
249+
}}
250+
/>
251+
```
252+
172253
### Adornments
173254

174255
You can use the `startAdornment` and `endAdornment` props to add a prefix or suffix, respectively, to a Number Input.

docs/pages/base-ui/api/use-number-input.json

+10-10
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
"min": { "type": { "name": "number", "description": "number" } },
1515
"onBlur": {
1616
"type": {
17-
"name": "(event?: React.FocusEvent) =&gt; void",
18-
"description": "(event?: React.FocusEvent) =&gt; void"
17+
"name": "(event?: React.FocusEvent&lt;HTMLInputElement&gt;) =&gt; void",
18+
"description": "(event?: React.FocusEvent&lt;HTMLInputElement&gt;) =&gt; void"
1919
}
2020
},
2121
"onChange": {
@@ -67,29 +67,29 @@
6767
},
6868
"getDecrementButtonProps": {
6969
"type": {
70-
"name": "&lt;TOther extends Record&lt;string, any&gt; = {}&gt;(externalProps?: TOther) =&gt; UseNumberInputDecrementButtonSlotProps&lt;TOther&gt;",
71-
"description": "&lt;TOther extends Record&lt;string, any&gt; = {}&gt;(externalProps?: TOther) =&gt; UseNumberInputDecrementButtonSlotProps&lt;TOther&gt;"
70+
"name": "&lt;ExternalProps extends Record&lt;string, unknown&gt; = {}&gt;(externalProps?: ExternalProps) =&gt; UseNumberInputDecrementButtonSlotProps&lt;ExternalProps&gt;",
71+
"description": "&lt;ExternalProps extends Record&lt;string, unknown&gt; = {}&gt;(externalProps?: ExternalProps) =&gt; UseNumberInputDecrementButtonSlotProps&lt;ExternalProps&gt;"
7272
},
7373
"required": true
7474
},
7575
"getIncrementButtonProps": {
7676
"type": {
77-
"name": "&lt;TOther extends Record&lt;string, any&gt; = {}&gt;(externalProps?: TOther) =&gt; UseNumberInputIncrementButtonSlotProps&lt;TOther&gt;",
78-
"description": "&lt;TOther extends Record&lt;string, any&gt; = {}&gt;(externalProps?: TOther) =&gt; UseNumberInputIncrementButtonSlotProps&lt;TOther&gt;"
77+
"name": "&lt;ExternalProps extends Record&lt;string, unknown&gt; = {}&gt;(externalProps?: ExternalProps) =&gt; UseNumberInputIncrementButtonSlotProps&lt;ExternalProps&gt;",
78+
"description": "&lt;ExternalProps extends Record&lt;string, unknown&gt; = {}&gt;(externalProps?: ExternalProps) =&gt; UseNumberInputIncrementButtonSlotProps&lt;ExternalProps&gt;"
7979
},
8080
"required": true
8181
},
8282
"getInputProps": {
8383
"type": {
84-
"name": "&lt;TOther extends Record&lt;string, any&gt; = {}&gt;(externalProps?: TOther) =&gt; UseNumberInputInputSlotProps&lt;TOther&gt;",
85-
"description": "&lt;TOther extends Record&lt;string, any&gt; = {}&gt;(externalProps?: TOther) =&gt; UseNumberInputInputSlotProps&lt;TOther&gt;"
84+
"name": "&lt;ExternalProps extends Record&lt;string, unknown&gt; = {}&gt;(externalProps?: ExternalProps) =&gt; UseNumberInputInputSlotProps&lt;ExternalProps&gt;",
85+
"description": "&lt;ExternalProps extends Record&lt;string, unknown&gt; = {}&gt;(externalProps?: ExternalProps) =&gt; UseNumberInputInputSlotProps&lt;ExternalProps&gt;"
8686
},
8787
"required": true
8888
},
8989
"getRootProps": {
9090
"type": {
91-
"name": "&lt;TOther extends Record&lt;string, any&gt; = {}&gt;(externalProps?: TOther) =&gt; UseNumberInputRootSlotProps&lt;TOther&gt;",
92-
"description": "&lt;TOther extends Record&lt;string, any&gt; = {}&gt;(externalProps?: TOther) =&gt; UseNumberInputRootSlotProps&lt;TOther&gt;"
91+
"name": "&lt;ExternalProps extends Record&lt;string, unknown&gt; = {}&gt;(externalProps?: ExternalProps) =&gt; UseNumberInputRootSlotProps&lt;ExternalProps&gt;",
92+
"description": "&lt;ExternalProps extends Record&lt;string, unknown&gt; = {}&gt;(externalProps?: ExternalProps) =&gt; UseNumberInputRootSlotProps&lt;ExternalProps&gt;"
9393
},
9494
"required": true
9595
},

0 commit comments

Comments
 (0)