-
Notifications
You must be signed in to change notification settings - Fork 47k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Set the correct initial value on input range #12939
Conversation
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed. If you have received this in error or have any questions, please contact us at cla@fb.com. Thanks! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this @Illu! I think this is good overall. I'm slightly worried that it sets currentValue
to the wrong value in cases where it has a valid initial value.
For example, if you're hydrating server-rendered markup the value will be already be correct because the element wasn't created with DOM APIs. This solution doesn't break that because initialValue
is still the source of truth for the current value, but it might be confusing if we ever use currentValue
in another way here.
We could potentially avoid this by using a more explicit check like:
if (currentValue === '') {
// ...
} else if (props.type === 'range') {
node.value = initialValue
}
In either case it would be great if we could add a comment explaining what's happening. Something like:
With range inputs node.value may be a default value calculated from the min/max attributes. This ensures that node.value is set with the correct value coming from props.
Thanks a lot for your feedback ! I'll take your recommendations and update the PR. |
Hey! This is on my radar. I have to wrap some stuff at work, but then I can definite get eyes on this today. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry that this will cause range inputs to revert any user interaction. Basically:
- Server-rendered markup comes down with a range input.
- The user slides a range input
- JavaScript executes
- React incorrectly reverts the value of the range input
I've created two test cases that I think prove this, do you mind if I submit them to this pull request?
@nhunzaker it seems like there isn't a way to identify whether a range input's value is from user interaction before hydration or calculated based on min/max.
For controlled inputs, isn't that what should happen? If the state is initialized to |
@aweary take a look at this test: it('should not blow away user-entered text on successful reconnect to a controlled input', async () => {
let changeCount = 0;
await testUserInteractionBeforeClientRender(
<ControlledInput onChange={() => changeCount++} />,
);
// note that there's a strong argument to be made that the DOM revival
// algorithm should notice that the user has changed the value and fire
// an onChange. however, it does not now, so that's what this tests.
expect(changeCount).toBe(0);
}); I think, right now, the current behavior is that it doesn't revert the state. And it also doesn't fire a change event (which seems like a bug to me, but prior work seems to acknowledge that). |
Is it possible that the root of the problem is that the guard to determine if a SSR input was changed is wrong? if (currentValue === '') I wonder if this should be: if (currentValue === node.defaultValue) Server rendered markup will have a value attribute (defaultValue) so:
Either way, @Illu: do you know if it is possible to get unit test coverage on the incorrect change event firing? It would be awesome if we could automate verifying this. |
Took a bit more time to understand this. I think the root of the problem is that our check for modified SSR markup is too naive. I don't know the best way to do this, but one option is to simply check to see if a value attribute is already defined on the input.
For the SSR case, the value attribute does the job of assigning an initial value to the input. But is this the best check to verify markup is hydrating? Could we pass this information into ReactDOMFiberInput? Here's a diff of my proposed changes, which I maintains the fix provided in this PR: |
😱 I didn't realize that. I can see why we'd do that to avoid losing user input, but it totally puts the DOM out of sync with state which seems really bad. It assumes the user will continue updating the value after hydration.
The problem with that is that const input = document.createElement('input');
input.type = 'range';
input.value;
// > "50"
input.defaultValue
// > "" |
@aweary you know... I wonder if we could fire a change event if we know an input is already on the page and its value differs from what React things. That could totally live in the postMountHook. |
@nhunzaker I was just thinking the same thing. We could check if Also, I think the |
Sorry to rapid fire.... I think it would be a huge win if we could avoid a heuristic to check of an input was rendered server side. Like pass a boolean if |
@nhunzaker the call sites for the post mount hooks differ for CSR ( |
Hi, sorry for this late comment. Is this test valid? I been able to compute value is equal to 0 and both onInput and onChange fired and onInput did always fire in the browser https://codesandbox.io/s/81m5w3q4ql (max=60) but onChange MOST IMPORTANT! the defaultValue reflects the previous value, the value tracker and node value are corrent https://codesandbox.io/s/5zon211194 onInput gives the correct results https://codesandbox.io/s/lr2x77np87 @aweary @nhunzaker So, I think it is not related to value, but event binding Which related to this function
I think the problem is the tracker doesn't exists, which return false in the function above |
I've applied @nhunzaker 's changes and added the However I didn't manage to create a unit test for the incorrect event firing yet. |
1476d65
to
4d2fe9b
Compare
4d2fe9b
to
cc2d609
Compare
@awery Could you explain more why this works? Why we need to set the value of range here? Because, the value of the range was always zero. Or the control of the range will be at end (value = 10). Let me show you https://whs-dot-hk.github.io/react-bug-12926-test/ with this change
I just added |
|
||
// Do not assign value if it is already set. This prevents user text input | ||
// from being lost during SSR hydration. | ||
if (currentValue === '') { | ||
if (!node.hasAttribute('value')) { | ||
// Do not re-assign the value property if there is no change. This | ||
// potentially avoids a DOM write and prevents Firefox (~60.0.1) from | ||
// prematurely marking required inputs as invalid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woah nice. You added the isHydrating
part! Very cool. I have a few questions:
- Can you use
isHydrating
as the condition on line 129, likeif (!isHydrating)?
- Are the lines between (218-225) necessary? I think the bug was that
range
inputs never reported""
, as required by the old code on line 229, that prevented the value assignment from happening.
Basically, I think the only change we need to make is on line 229:
if (!isHydrating) {
// ....
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, you're absolutely right! I'll update it right away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks 💰 to me. @aweary do you mind taking a quick look?
Separately, we now have a pretty convenient place to trigger a change event for SSR hydrated inputs. I've jotted down a reminder to write up a ticket for that.
I dig into the factory ReactDOMFiberComponent.js and make this fix hostProps happen to be before the value tracker is register The demo is here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! I love how more much explicit the hydration case is. This does make ReactDOMFiberInput.postMountWrapper
inconsistent with the other postMountWrapper
methods, but that's probably fine.
Separately, we now have a pretty convenient place to trigger a change event for SSR hydrated inputs. I've jotted down a reminder to write up a ticket for that.
@nhunzaker I opened #12955 to track.
@gaearon does this look OK to you?
I think this makes sense |
Awesome. Thanks @Illu! This was a wonderful fix and a very insightful discussion. |
193: Update dependency react to v16.4.1 r=magopian a=renovate[bot] This Pull Request updates dependency [react](https://github.com/facebook/react) from `v16.4.0` to `v16.4.1` <details> <summary>Release Notes</summary> ### [`v16.4.1`](https://github.com/facebook/react/blob/master/CHANGELOG.md#​1641-June-13-2018) [Compare Source](facebook/react@v16.4.0...v16.4.1) ##### React * You can now assign `propTypes` to components returned by `React.ForwardRef`. ([@​bvaughn] in [#​12911](`https://github.com/facebook/react/pull/12911`)) ##### React DOM * Fix a crash when the input `type` changes from some other types to `text`. ([@​spirosikmd] in [#​12135](`https://github.com/facebook/react/pull/12135`)) * Fix a crash in IE11 when restoring focus to an SVG element. ([@​ThaddeusJiang] in [#​12996](`https://github.com/facebook/react/pull/12996`)) * Fix a range input not updating in some cases. ([@​Illu] in [#​12939](`https://github.com/facebook/react/pull/12939`)) * Fix input validation triggering unnecessarily in Firefox. ([@​nhunzaker] in [#​12925](`https://github.com/facebook/react/pull/12925`)) * Fix an incorrect `event.target` value for the `onChange` event in IE9. ([@​nhunzaker] in [#​12976](`https://github.com/facebook/react/pull/12976`)) * Fix a false positive error when returning an empty `<React.Fragment />` from a component. ([@​philipp-spiess] in [#​12966](`https://github.com/facebook/react/pull/12966`)) ##### React DOM Server * Fix an incorrect value being provided by new context API. ([@​ericsoderberghp] in [#​12985](`https://github.com/facebook/react/pull/12985`), [@​gaearon] in [#​13019](`https://github.com/facebook/react/pull/13019`)) ##### React Test Renderer * Allow multiple root children in test renderer traversal API. ([@​gaearon] in [#​13017](`https://github.com/facebook/react/pull/13017`)) * Fix `getDerivedStateFromProps()` in the shallow renderer to not discard the pending state. ([@​fatfisz] in [#​13030](`https://github.com/facebook/react/pull/13030`)) --- </details> --- This PR has been generated by [Renovate Bot](https://renovatebot.com). Co-authored-by: Renovate Bot <bot@renovateapp.com>
25: Update react monorepo to v16.4.1 r=renovate[bot] a=renovate[bot] This Pull Request renovates the package group "react monorepo". - [react-dom](https://github.com/facebook/react) (`dependencies`): from `16.4.0` to `16.4.1` - [react](https://github.com/facebook/react) (`dependencies`): from `16.4.0` to `16.4.1` # Release Notes <details> <summary>facebook/react</summary> ### [`v16.4.1`](https://github.com/facebook/react/blob/master/CHANGELOG.md#​1641-June-13-2018) [Compare Source](facebook/react@v16.4.0...v16.4.1) ##### React * You can now assign `propTypes` to components returned by `React.ForwardRef`. ([@​bvaughn] in [#​12911](`https://github.com/facebook/react/pull/12911`)) ##### React DOM * Fix a crash when the input `type` changes from some other types to `text`. ([@​spirosikmd] in [#​12135](`https://github.com/facebook/react/pull/12135`)) * Fix a crash in IE11 when restoring focus to an SVG element. ([@​ThaddeusJiang] in [#​12996](`https://github.com/facebook/react/pull/12996`)) * Fix a range input not updating in some cases. ([@​Illu] in [#​12939](`https://github.com/facebook/react/pull/12939`)) * Fix input validation triggering unnecessarily in Firefox. ([@​nhunzaker] in [#​12925](`https://github.com/facebook/react/pull/12925`)) * Fix an incorrect `event.target` value for the `onChange` event in IE9. ([@​nhunzaker] in [#​12976](`https://github.com/facebook/react/pull/12976`)) * Fix a false positive error when returning an empty `<React.Fragment />` from a component. ([@​philipp-spiess] in [#​12966](`https://github.com/facebook/react/pull/12966`)) ##### React DOM Server * Fix an incorrect value being provided by new context API. ([@​ericsoderberghp] in [#​12985](`https://github.com/facebook/react/pull/12985`), [@​gaearon] in [#​13019](`https://github.com/facebook/react/pull/13019`)) ##### React Test Renderer * Allow multiple root children in test renderer traversal API. ([@​gaearon] in [#​13017](`https://github.com/facebook/react/pull/13017`)) * Fix `getDerivedStateFromProps()` in the shallow renderer to not discard the pending state. ([@​fatfisz] in [#​13030](`https://github.com/facebook/react/pull/13030`)) --- </details> --- This PR has been generated by [Renovate Bot](https://renovatebot.com). Co-authored-by: Renovate Bot <bot@renovateapp.com>
Resolves #12926
Sets the value of the input range to empty, instead of the default value from the input component.
This prevents the input from not updating when first switching to the default value (or
max
value in case ofmax
being inferior to this default value (50))