# SpinButton onChange behavior
Largely based on discussion from https://github.com/microsoft/fluentui/issues/5326 -- I think this is pretty much the same proposal that the discussion thread eventually arrived at.
### Intermediate vs. committed values
SpinButton can have two types of values: **intermediate** and **committed**.
A value is **intermediate** at any of the following times:
- User is editing the text field
- (possibly) User is spinning via mouse or keyboard
The value is **committed** when any of the following happens:
- Blur
- Enter key press (while editing field)
- On each spin *(see discussion later)*
The commit phase consists of these steps:
1. Call `onIncrement`, `onDecrement`, or `onValidate` as appropriate
2. Only if the value changed, call `onChange` (if provided)
3. Clear the intermediate value
4. Switch to displaying the committed value:
- If uncontrolled, update the stored value and display that
- If controlled, display `props.value` (which the user likely updated when `onChange` was called; we do NOT store the value in any other internal state in this case)
### Determining and storing the displayed value
Where is the value stored?
- Controlled: use `props.value` except while editing
- Uncontrolled: ~~`uncontrolledValue` state~~ `value` returned by `useControllableValue`
- While editing: `intermediateValue` state (or a better name)
What is the initial value? (in order of precedence)
1. `props.value` (no validation)
2. `defaultValue` (no validation)
3. `min` (which defaults to 0)
What is the currently displayed value?
1. `intermediateValue` if set
2. `props.value` if provided (controlled)
3. `uncontrolledValue` (uncontrolled)
What is the value of `ISpinButton.value`?
1. `props.value` if provided (controlled)
2. `uncontrolledValue` (uncontrolled)
3. Probably should NOT ever reflect `intermediateValue` here
Edge cases:
- If the user is typing and deletes the value (without committing), don't replace it with the previous value or anything else (be careful with falsy checks)
- Possibly: While editing, if the parent re-renders with the *same* `props.value` we should *retain* `intermediateValue` (remain in edit mode). But if it re-renders with a different `props.value`, we should *clear* `intermediateValue`.
### When is `onChange` called?
`onChange` should only be called in the commit phase as described above, **not on every keystroke** ~~or spin~~.
This is the sequence of events each time `onChange` is called:
1. If value changed, call `onChange`
2. If applicable, clear `intermediateValue`
- If controlled, this should cause displayed value to revert to `props.value`, whether or not it's been updated
3. If uncontrolled, update `uncontrolledValue`
Details of each case where `onChange` can be called:
**Blur**
1. Call `onValidate`
2. `onChange` sequence (see below)
**Enter keypress**: same as blur
**Arrow button click or spin**
1. If the user was already typing (`intermediateValue` is set):
1. `onBlur` should be triggered, which should call `onValidate` + `onChange` sequence and likely a value update
2. *Wait for the value to be updated* (either setting `uncontrolledValue` state or the parent updating `props.value`)
- Will need to think about how to implement this for controlled: must ensure that if the parent updates `props.value` based on `onChange`, we use that new value for the arrow button action
- (This handles the case where the user types garbage and starts spinning)
2. On each increment, call `onIncrement`/`onDecrement` followed by `onChange` sequence
- ~~*Open question: should we call `onChange` at this point? (maybe not? though that's harder to implement and validate, and different from previous behavior)*~~
3. ~~*(if we decide not to call every spin)* After spinning stops, `onChange` sequence~~
**Arrow key press or spin**
1. If the user was already typing (`intermediateValue` is set):
1. Call `onValidate`
2. `onChange` sequence
3. *Wait for the value to be updated* (same race condition issue as buttons)
2. Same behavior as with buttons
### Questions
- Should we ever validate `props.value`?
- I think not, to follow pattern of other components?
- Should we validate `defaultValue`?
- I think not
- Should `onChange` be fired for every incremental value while spinning?
- After discussion with David, **yes** we should call it every spin. One reason being because it's not uncommon that on every press, something in the app will react (kind of like with a slider) -- like a font size going up and down
- Previous thoughts:
- Might prefer not to, but that would be inconsistent with when `onIncrement`/`onDecrement` are called *and* inconsistent with previous behavior. (Also a bit harder to implement.)
- The reason it's inconsistent with previous behavior is because we'd only update `ISpinButton.value` at the same times as `onChange` is called. Right now `ISpinButton.value` is updated on every spin.
- What should `ISpinButton.value` return? Probably the committed value, but is there any scenario where we ought to provide access to the intermediate value? (besides tests)
- Should we make the component fully controllable with just `onIncrement`/`onDecrement`/`onValidate`?
- There are likely some people who have it implemented this way today since that was the only way in the past to have a fully controlled component *(verify)*. But it would probably be hard to make both approaches work simultaneously.
### Not supported
- Implementing all of increment/decrement/validate using just `onChange`. This would cause the `onChange` signature to be very convoluted (and it would be hard to provide default inc/dec/val handlers).
- Calling `onChange` for any other reason besides changes from the user (it shouldn't be called on internal validation or updates of props or state)
### Notes
- Release notes
- Providing `value` now results in a controlled component (probably wasn't the case before; *this is one reason it's a breaking change*)
Tests (see https://github.com/microsoft/fluentui/pull/7577):
- It should call onChange with normalized value when input box blurs.
- It should call onChange when click increase or decrease button.
- It should revert to the original valid value if user inputs some random string.