-
Notifications
You must be signed in to change notification settings - Fork 47.5k
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
Make sure that select
has multiple
attribute set to appropriate state before appending options
#13270
Conversation
…tate before appending options fixes facebook#13222
ReactDOM: size: 0.0%, gzip: 0.0% Details of bundled changes.Comparing: b565f49...afedb68 react-dom
Generated by 🚫 dangerJS |
Awesome. Thank you for getting to the bottom of this! I'm looking into whether or not this is the best place to ensure Could you check to see if JSDOM sets the scroll position on multi-selects? If so, I'd like to add a test for this here: Otherwise, I'd like to get manual dom test fixture for this. These live here: And we should add a test case to this module: Please let me know if you hit a snag, I'd be happy to provide additional assistance! |
…rst selected option fixes facebook#13222
@nhunzaker No problem. I think there is really not so much options where to ensure that
I don't think it is possible to do with JSDOM. And even if it had been possible it would have been tested only against JSDOM. Do not see much sense in this. I've added manual dom fixture. |
@@ -158,6 +158,25 @@ class SelectFixture extends React.Component { | |||
</form> | |||
</div> | |||
</TestCase> | |||
|
|||
<TestCase title="A multiple selec being scrolled to first selected option"> |
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.
Typo: selec
Can this be colocated with other ReactDOMFiberSelect logic? |
473b52f
to
d7c57e5
Compare
I don't think so. At least I don't see a way how to do this. |
Can you explain why? I’m curious why we usually adjust properties later but here we have to do it right after creation. What’s special? |
Just to recap, the issue is that the It looks like we were actually doing this in the But, by this point, the option items are already appended to the select. This is a change in the order of operations from React 15 to React 16 (children are now appended before attributes are added): What if we made a That could look like: export function createElement(
type: string,
props: Object,
rootContainerElement: Element | Document,
parentNamespace: string,) {
// ...
switch (type) {
case 'select'
ReactDOMFiberSelect.createHook(props);
break;
}
} Though for this case, since it's the only element so far that has an issue, maybe we could just use an |
The comment should say this. |
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 all right to me. Posted some nits.
@nhunzaker Please feel free to merge once they’re addressed.
// Make sure that `select` has `multiple` attribute set to appropriate state before appending options | ||
// To prevent first option be initialy made selected | ||
// see more details in https://github.com/facebook/react/issues/13222 | ||
if (type === 'select' && !!props.multiple) { |
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.
In this context !!
is redundant. You can drop it.
@@ -378,6 +378,12 @@ export function createElement( | |||
// See discussion in https://github.com/facebook/react/pull/6896 | |||
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240 | |||
domElement = ownerDocument.createElement(type); | |||
// Make sure that `select` has `multiple` attribute set to appropriate state before appending options | |||
// To prevent first option be initialy made selected | |||
// see more details in https://github.com/facebook/react/issues/13222 |
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.
Please change the comment to the suggestion in the discussion thread
@@ -378,6 +378,12 @@ export function createElement( | |||
// See discussion in https://github.com/facebook/react/pull/6896 | |||
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240 | |||
domElement = ownerDocument.createElement(type); | |||
// Make sure that `select` has `multiple` attribute set to appropriate state before appending options | |||
// To prevent first option be initialy made selected | |||
// see more details in https://github.com/facebook/react/issues/13222 |
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 isn't perfect, but could you update this comment to read like:
// Normally attributes are assigned in `setInitialDOMProperties`, however the `multiple`
// attribute on `select`s needs to be added before `option`s are inserted. This prevents
// a bug where the `select` does not scroll to the correct option because singular
// `select` elements automatically pick the first item.
// See https://github.com/facebook/react/issues/13222
@gaearon Could this be clearer?
// To prevent first option be initialy made selected | ||
// see more details in https://github.com/facebook/react/issues/13222 | ||
if (type === 'select' && !!props.multiple) { | ||
domElement.setAttribute('multiple', 'true'); |
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.
We use property assignment in the postMountWrapper, could you do the same 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.
postMountWrapper
attempts to update options, but as at this moment select doesn't have any child appended, it makes no sense, it will iterate over empty options collection. However it should not cause any problems so if you think it is better approach I can do this change.
What if |
remove redundant conversion to bool type
Could we consider changing the order back to match React 15? I brought this up a while back but there was no follow up on it. Was reordering these operations intentional? |
@gaearon Google chrome scrolls to selected option only once.
So any further changes won't change scroll position. |
Reordering was done in ea34204 as far as I can see. Ironically it was partially done because of
That said we can probably split setting initial properties in two phases as well (before and after appending the children). Do we have a proposal for which ones should go where? |
I do not know about other cases when it is necessary to assign properties before appending the children. As it turned out this issue (as well as solution) was known long time ago. So, probably there are some other related issues in the list. |
What about the suggested fix in #12242 (comment)? Would that also solve the issue? |
Well, it solves only issue described in the pull request title. But, this change doesn't fix the scrolling issue. |
Okay. That sounds good then. Can you please fix conflicts/CI? |
Requested changes look good, though I think we still need to answer whether or not we're okay placing this fix where it is at.
I'm not sure, but:
What if we just placed a hook here: react/packages/react-dom/src/client/ReactDOMHostConfig.js Lines 183 to 192 in 28cd494
Maybe something like: // ... createInstance()
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
// Open to a better name
setPropsBeforeChildren(type, props, domElement)
return domElement;
} Then in ReactDOMFiberComponent: function setPropsBeforeChildren(type, props, domElement) {
if (type === 'select' && props.multiselect) {
select.multiselect = true
}
} |
@nhunzaker Is your suggestion technically different except code organization? I'm fine with either tbh. |
I guess a separate function makes it a bit harder to miss why we're doing it. And GCC will inline it anyway. |
@gaearon I don't think it introduces anything new. Purely code organization. Honestly I'm okay with this how it is, though I do think it's worth starting catalog why these sorts of fixes need to happen at specific points in the process for a later refactor. There isn't really great place for a fix like this and adding an extra layer of indirection doesn't feel right. |
// `select` elements automatically pick the first item. | ||
// See https://github.com/facebook/react/issues/13222 | ||
if (type === 'select' && props.multiple) { | ||
domElement.setAttribute('multiple', 'true'); |
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.
Just for consistence with ReactDOMFiberSelect, can you assign this as a property, like:
domElement.multiple = true
Might want to start a document with a list of hacks for any input type and reasons for them. |
Sounds good. I'll start a markdown document and add it to the fixtures. Maybe it could be hosted along side a public version of the fixtures using Netlify (need to circle back to that too). |
@gaearon This looks good on my end and I have verified it locally. |
@segoddnja I pushed a small commit to remove an unused ref just now, but I believe this is good to merge. |
Applying multiple options to
select
node before it's multiple attribute is set totrue
leads to the first option be selected by default, event in the case there is novalue
ordefaultValue
passed in the props.It leads to another undesired outcome. As setting real
selected
attributes values happens in the post mount wrapper, browser does not scroll list to selected option if it is outside of the select.fixes #13222