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
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions packages/react-dom/src/__tests__/ReactDOMComponent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 the value to a string.',
);
});

Expand All @@ -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 the value to a string.',
);
});

Expand Down Expand Up @@ -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 the value to a string.',
);
});

Expand Down Expand Up @@ -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.

'instead of {`whatever` && value}.',
);
});
});
Expand Down
35 changes: 26 additions & 9 deletions packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,32 @@ if (__DEV__) {
return true;
}

if (typeof value === 'boolean') {
warning(
DOMProperty.shouldAttributeAcceptBooleanValue(name),
'Received `%s` for non-boolean attribute `%s`. If this is expected, cast ' +
'the value to a string.%s',
value,
name,
getStackAddendum(),
);
if (
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)

warning(
false,
'Received `%s` for non-boolean attribute `%s`. If this is expected, cast ' +
'the value to a string.%s',
value,
name,
getStackAddendum(),
);
} else {
warning(
false,
'Received `%s` for non-boolean attribute `%s`. If you mean to conditionally ' +
'pass an attribute, use a ternary expression: {`%s` ? value : undefined} ' +
'instead of {`%s` && value}.%s',
value,
name,
name,
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?

}
Expand Down