Skip to content
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

Select element update #7054

Closed
wants to merge 18 commits into from

Conversation

git-richard
Copy link

I saw some others might be working on this issue but I'm submitting a PR to get some practice. Don't mean to step on any toes.

The code loops through the options in a select element and displays a warning if there are duplicate option values. There are more efficient array comparison algorithms but this works well. It shows all duplicate values rather than exiting the evaluation loops on the first dupe it finds.

Appreciate any feedback.

@git-richard
Copy link
Author

Leave a comment

@@ -195,6 +217,9 @@ var ReactDOMSelect = {
);
didWarnValueDefaultValue = true;
}

warnIfDuplicateValues(inst);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be placed within the _DEV_ block at the top of the function so it only runs in development.

@git-richard git-richard reopened this Jun 16, 2016
@@ -58,6 +58,28 @@ function warnIfValueIsNull(props) {
}
}

function warnIfDuplicateValues(inst) {
const options = inst._currentElement.props.children;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is legal for this to be a composite component, so the children might not be simple option tags.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They can also be nested inside arrays/maps/sets etc, so this does not work reliably.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not to forget that <optgroup> is a thing.

Copy link
Contributor

@syranide syranide Jun 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, children can be null or an element (not wrapped in an array). I'm not sure whether it is supported or not yet, but IIRC the idea is also to support anything that is iterable as valid value for children, so we can't assume this to be an array in that case?

@ghost
Copy link

ghost commented Jun 16, 2016

@git-richard updated the pull request.

if (options[i].props.value === options[j].props.value) {
warning(
false,
`Select element contains duplicate option value ${options[i].props.value} in options #${i} & #${j}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We try to include a stack trace for new warnings. Again, #7040 is a good example.

@git-richard
Copy link
Author

I updated the code based on previous feedback. Moved to the dev tools. It only processes controlled select elements. Processes option and optGroup children. Iterates the children instead of processing as an array. Shows the stack trace in the warning. A few other changes. Any feedback is appreciated.

}

// Check the array for duplicate values.
for (var i = 0; i < values.length-1; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This strategy for duplicate checking is a bit slow: O(n²)

We can check for duplicates in the first loop (line 52) by making values a dictionary and checking if the current value has already been seen:

const values = {};

for (const option of options) {
  ... // code for handling optgroups

  if (option.type === 'option'
    && option.props != null 
    && option.props.value) {
      const value = option.props.value;

      if (!values[value]) {
        values[value] = value;
      } else {
        // warn about duplicate
        return;
      }
  }
}

However, in reality, the chances of a <select> having enough <options>s to make this matter is small, but doing it this way does save us some lines of code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback. I updated it to use the dictionary but found an issue so I reverted back to the arrays. The dictionary approach shows a warning for a select like this:

<select>
    <option value={{val: 'a'}}>a</option>
    <option value={{val: 'b'}}>b</option>
</select>

Those are different values and shouldn't show a warning. I agree that a select shouldn't normally have enough options for the array comparisons to be a performance issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@git-richard The value is stringified (inspect the DOM) so in your case they are actually the same.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I didn't notice that. Do you think using a dictionary of the options, as mentioned above, is a correct approach? Thanks,

Copy link
Contributor

@syranide syranide Jul 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we can avoid O(n^2) definitely seems favorable I'd say. EDIT: For most it probably doesn't matter, but consider it's just a warning, ensuring it never balloons seems like a good idea.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it back to a dictionary. Do you see any other issues with it?

if (option.type === 'option') {
if ((option.props != null) && (option.props.value)) {
const value = option.props.value;
if (!values[value]) {
Copy link
Contributor

@syranide syranide Jul 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to use hasOwnProperty. I'm not sure if it really matters, but it may make sense to explicitly stringify value as well?

@chicoxyzzy
Copy link
Contributor

chicoxyzzy commented Jul 5, 2016

Still don't sure this will solve the problem as mentioned in #6959 (comment)

LGTM

}

if (option.type === 'optGroup') {
if ((option.props != null) && (option.props.children != null)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be great if we can merge this two ifs.

@montogeek
Copy link
Contributor

It is this still relevant?

@aweary
Copy link
Contributor

aweary commented Sep 25, 2017

@montogeek nope, these files don't exist anymore. Thanks for the heads up!

@aweary aweary closed this Sep 25, 2017
@gaearon
Copy link
Collaborator

gaearon commented Sep 25, 2017

What was the relevant issue?

@montogeek
Copy link
Contributor

From the original message: "...displays a warning if there are duplicate option values." Then he introduced an array comparison algorithm

@aweary
Copy link
Contributor

aweary commented Sep 25, 2017

The issue this was meant to address is #6959.

@git-richard I'm sorry we didn't provide any feedback on this. Sometimes low priority issues slip through the cracks. It's been over a year since this PR was opened, and the codebase has seen substantial changes. If you want to keep working on this, I would suggest looking at ReactDOMFiberComponent and how it uses modules like ReactDOMUnkownPropertyHook for DEV-only warnings.

Feel free to open a new PR for this if you do choose to pursue it, and also feel free to ping us with questions. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants