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

Fixed invalid prop types error message to be more specific #11308

Merged
merged 22 commits into from
Oct 31, 2017

Conversation

NicBonetto
Copy link
Contributor

Before submitting a pull request, please make sure the following is done:

  1. Fork the repository and create your branch from master.
  2. Run yarn in the repository root.
  3. If you've fixed a bug or added code that should be tested, add tests!
  4. Ensure the test suite passes (yarn test). Tip: yarn test --watch TestName is helpful in development.
  5. Format your code with prettier (yarn prettier).
  6. Make sure your code lints (yarn lint). Tip: yarn linc to only check changed files.
  7. Run the Flow typechecks (yarn flow).
  8. If you haven't already, complete the CLA.

Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html

@NicBonetto
Copy link
Contributor Author

#11289

'the value to a string.%s',
'If you intentionally tried to pass a boolean, pass it as a string ' +
'instead: `%s`="`%s`". If you mean to conditionally pass an ' +
'attribute, use a ternary expression: `%s`={condition ? value : null}.%s',
Copy link
Collaborator

@sebmarkbage sebmarkbage Oct 21, 2017

Choose a reason for hiding this comment

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

If the value is true then this is very unlikely to happen because the problematic pattern is condition && value specifically. I can't think of a way where you'd end up with true for the same reason. Maybe we should have two separate error messages for true and false where this recommendation is only for the false case?

Maybe also call out that the problematic pattern is condition && value specifically, so that people know what's wrong and what to look for along with your proposed recommendation.

Also, the recommendation should be to pass undefined, not null. It doesn't really matter for DOM components, but if it is a custom component that is the difference between defaultProps working and not working. So it's better to have consistent advice.

@NicBonetto
Copy link
Contributor Author

I may need some help with the error that occurred in the integration test. I believe it is coming from adding an additional condition in the if statement, and I would appreciate a little direction. Thank you.

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

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

The overall structure is a bit hard to understand.

It's currently:

if (a && b) {
  warning(c, ...)
  return true
} else if (a) {
  warning(c, ...)
  //  warnedProps[...] = true <-- missing for some reason
  // return true <-- missing for some reason
}

Can you think of ways to simplify it?

For example:

if (a && !c) {
  if (b) {
    warning(false, ...)
  } else {
    warning(false, ...)
  }
  warnedProps[...] = true
  return true
}

@@ -189,8 +200,6 @@ if (__DEV__) {
name,
getStackAddendum(),
);
warnedProperties[name] = true;
return true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why was this removed?

if (typeof value === 'boolean') {
if (value === true) {
warning(
DOMProperty.shouldAttributeAcceptBooleanValue(name),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we make this warning(false and move out this (negated) condition together with the top one?

Copy link
Contributor Author

@NicBonetto NicBonetto Oct 26, 2017

Choose a reason for hiding this comment

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

Yes :) Just so I have a correct understanding, the refactor will look like:

if (typeof value === 'boolean' && !false) {
  if (value === true) {
    warning(false,
      ...
    );
  } else {
    warning(false,
      ...
    );
  }
  ...
}

Is this the branching logic you prefer? I do not understand the !false condition, so if I am incorrect please explain what should take it's place.

Copy link
Collaborator

Choose a reason for hiding this comment

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

if (typeof value === 'boolean' && !DOMProperty.shouldAttributeAcceptBooleanValue(name)) {
  if (value === true) {
    warning(false,
      ...
    );
  } else {
    warning(false,
      ...
    );
  }
  ...
}

@@ -54,8 +54,8 @@ describe('ReactDOM unknown attribute', () => {
expectDev(
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
).toMatch(
'Warning: Received `true` for non-boolean attribute `unknown`. ' +
'If this is expected, cast the value to a string.\n' +
'Warning: Received `true` for non-boolean attribute `unknown`. If this is expected, cast ' +
Copy link
Collaborator

@gaearon gaearon Oct 26, 2017

Choose a reason for hiding this comment

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

This change is unnecessary, right? It just reformats the message in a slightly less readable way.

@@ -2042,7 +2042,8 @@ describe('ReactDOMComponent', () => {
expect(el.hasAttribute('whatever')).toBe(false);

expectDev(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Received `true` for non-boolean attribute `whatever`',
'Received `true` for non-boolean attribute `whatever`. If this is expected, cast ' +
Copy link
Collaborator

@gaearon gaearon Oct 26, 2017

Choose a reason for hiding this comment

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

Would look nicer if If this is expected... started on the new line in test.

@@ -2055,7 +2056,8 @@ describe('ReactDOMComponent', () => {
expect(el.hasAttribute('whatever')).toBe(false);

expectDev(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Received `true` for non-boolean attribute `whatever`',
'Received `true` for non-boolean attribute `whatever`. If this is expected, cast ' +
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same

@@ -2230,7 +2232,8 @@ describe('ReactDOMComponent', () => {
expect(el.hasAttribute('whatever')).toBe(false);

expectDev(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Received `true` for non-boolean attribute `whatever`.',
'Received `true` for non-boolean attribute `whatever`. If this is expected, cast ' +
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same

'Warning: Received `false` for non-boolean attribute `whatever`.',
'If you mean to conditionally pass an attribute, use a ternary ' +
'expression: `false`={condition ? value : undefined} instead of ' +
'{condition && value}.',
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does false={condition? value : undefined} mean? Why does it says false?

Please fix this to show the attribute name.

} else {
warning(
false,
'If you mean to conditionally pass an attribute, use a ternary ' +
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this also start with Received ... for non-boolean attribute ...? I want both messages to look similar, but with different second sentences.

@gaearon
Copy link
Collaborator

gaearon commented Oct 28, 2017

I tried to build React locally and then used

ReactDOM.render(
   <h1 myattribute={false}>Hello World!</h1>,
    document.getElementById('container')
);

I saw this message:

screen shot 2017-10-28 at 6 50 25 pm

Does it make sense to you? I don't think its says what it's supposed to say.

@@ -2283,7 +2286,9 @@ describe('ReactDOMComponent', () => {

expectDev(console.error.calls.count()).toBe(1);
expectDev(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Received `false` for non-boolean attribute `whatever`.',
'Received `false` for non-boolean attribute `whatever`. If you mean to conditionally ' +
'pass an attribute, use a ternary expression: {`whatever` ? value : undefined} ' +
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please make sure to read the messages you assert in tests. It looks like you were trying to adjust tests to match the wrong behavior in the code, rather than the other way around.

Copy link
Contributor Author

@NicBonetto NicBonetto Oct 28, 2017

Choose a reason for hiding this comment

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

Sorry, my understanding was that the value being passed was the condition. So value should hold the attribute name instead of condition correct? It didn't make sense that whatever (holding the value false) could be an attribute. Thank you for your patience and help through this issue.

Copy link
Collaborator

@gaearon gaearon Oct 28, 2017

Choose a reason for hiding this comment

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

I’m sorry that I snapped. I had a bad day and shouldn’t have spoken to you with this tone. I apologize. In this test, whatever is the attribute name. Not the value. You can see if from the first sentence:

Received false for non-boolean attribute whatever.

Therefore, a condition like {whatever ? value : undefined} is not relevant to the user code. The user has something like whatever={condition && value} with condition being false. We want to tell the user to turn it into something like whatever={condition ? value : undefined}.

So I think ideally the messages would be:

  1. If you get true:
expectDev(console.error.calls.argsFor(0)[0]).toContain(
  'Received `true` for non-boolean attribute `whatever`.\n\n' +
  'If you want `true` to appear in the DOM, cast it to a string. ' +
  'For example, you can pass `whatever="true"` or `whatever={String(value)}` instead.'
);
  1. If you get false:
expectDev(console.error.calls.argsFor(0)[0]).toContain(
  'Received `false` for non-boolean attribute `whatever`.\n\n' +
  'If you want `false` to appear in the DOM, cast it to a string. ' +
  'For example, you can pass `whatever="false"` or `whatever={String(value)}`.\n\n' +
  'If you want to omit `whatever` based on a condition, use a ternary expression. ' +
  'For example, you can pass `whatever={condition ? value : undefined}` ' +
  'instead of `whatever={condition && value}`.'
);

In these examples, whatever should of course refer to the attribute name, not literally "whatever". I only used whatever because that's what the test already uses.

I hope this is helpful and clarifies things!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No need to apologize, your message did not come off badly. I only apologized for making you revise this pr so many times because I did not grasp the whole picture. Yes, this is extremely helpful. Thank you.

Copy link
Contributor Author

@NicBonetto NicBonetto Oct 28, 2017

Choose a reason for hiding this comment

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

Do you prefer:

`whatever={String(value)}`

or

`whatever={value.toString()}`

I feel the second option is a bit more verbose and easier to read.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I thought about the second one but it will throw for null/undefined. Might be unexpected. First one seems safer. Although on the other hand you probably don't want "undefined" or "null" as strings either.

I wish there was some concise way to express "stringify unless it's null/undefined".

I guess I like that value.toString() throws it it doesn't exist. If you mess it up you'll immediately realize it by the crash, instead of quietly using the wrong value. So let's suggest that.

typeof value === 'boolean' &&
!DOMProperty.shouldAttributeAcceptBooleanValue(name)
) {
if (value === true) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: we already know it's a boolean so can just write if (value)

@gaearon
Copy link
Collaborator

gaearon commented Oct 29, 2017

Thanks for the fixes!

I looked at it in practice, and I think it's a bit too noisy with backticks everywhere. Let's keep them around singular words (e.g. around attribute name), but remove them around fragments of code.

I also found the current messages too noisy and potentially misleading after all. Sorry about this—it takes a few iterations to get this right. Here's my new proposal.

For true:

expectDev(console.error.calls.argsFor(0)[0]).toContain(
  'Received `true` for non-boolean attribute `whatever`.\n\n' +
  'If you want to write it to the DOM, pass a string instead: ' +
  'whatever="true" or whatever={value.toString()}.'
);

screen shot 2017-10-29 at 17 51 51

For false:

expectDev(console.error.calls.argsFor(0)[0]).toContain(
  'Received `false` for non-boolean attribute `whatever`.\n\n' +
  'If you want to write it to the DOM, pass a string instead: ' +
  'whatever="false" or whatever={value.toString()}.\n\n' +
  'If you conditionally omit it with whatever={condition && value}, ' +
  'pass whatever={condition ? value : undefined} instead.'
);

screen shot 2017-10-29 at 17 51 42

I hope it’s not too much of a burden! I think these are clearer.

@gaearon
Copy link
Collaborator

gaearon commented Oct 31, 2017

I think this is good to go. Did a few minor changes. Thanks so much!

@gaearon gaearon merged commit 544d5c7 into facebook:master Oct 31, 2017
Ethan-Arrowood pushed a commit to Ethan-Arrowood/react that referenced this pull request Dec 8, 2017
…11308)

* Modified tests and corrected error message. facebook#3

* Fixed syntax issues. facebook#3

* Modified test. facebook#3

* Prettified. facebook#3

* Changed warning message to handle true and false boolean values. facebook#3

* Changed test to contain undefined instead of value. facebook#3

* Simplified branch structure. facebook#3

* Refactored branching logic. facebook#3

* Refactored falsy warning message and tests. facebook#3

* Changed condition to attribute name. facebook#3

* Refactored falsy and truthy warning messages with tests updated. facebook#3

* Added missing character. facebook#3

* Fixed warning message. facebook#3

* Cleared extra whitespace. facebook#3

* Refactored warning messages to be clear. facebook#3

* Prettified. facebook#3

* Grammar fix

* Tweak unrelated warning

The message didn't make sense because it appears for *any* attributes, not just numeric ones.

* Tweak the message for more clarity

* Add a special message for false event handlers

* Add missing whitespace

* Revert size changes
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.

4 participants