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

[Table] Validation improvements #1587

Merged
merged 11 commits into from
Sep 21, 2017
Merged

Conversation

cmslewis
Copy link
Contributor

@cmslewis cmslewis commented Sep 20, 2017

Fixes #1371

Checklist

  • Include tests

Changes proposed in this pull request:

  • 🐛 FIXED Table now validates props on mount and on update.
  • 🔄 CHANGED Table now validates props as follows (assuming each prop involved is defined):
    • Errors:
      • numRows < 0
      • numFrozenRows < 0
      • numFrozenColumns < 0
      • numRows !== rowHeights.length
      • numColumns !== columnWidths.length
      • non-<Column> child (*)
    • Warnings:
      • numFrozenRows > numRows (changed the message to mention that we'll clamp for you)
      • numFrozenColumns > numColumns (same)

Reviewers should focus on:

  • How does the error-message language look?
  • (*) Is this the right choice? Per my code comment, we're currently just printing a warning, but the Table would eventually throw a convoluted error from deep inside when processing non-<Column> children, so I figure we might as well just throw a clear error at the outset.

Screenshot

Validation
  on mount
    errors
      ✔ throws an error if numRows < 0
      ✔ throws an error if numFrozenRows < 0
      ✔ throws an error if numFrozenColumns < 0
      ✔ throws an error if rowHeights.length !== numRows
      ✔ throws an error if columnWidths.length !== number of <Column>s
      ✔ throws an error if a non-<Column> child is provided
    warnings
      ✔ should print a warning when numFrozenRows > numRows
      ✔ should print a warning when numFrozenColumns > num <Column>s
  on update
    errors
      ✔ on numRows update, throws an error if numRows < 0
      ✔ on numFrozenRows update, throws an error if numFrozenRows < 0
      ✔ on numFrozenColumns update, throws an error if numFrozenColumns < 0
      ✔ on numRows update, throws an error if rowHeights.length !== numRows
      ✔ on rowHeights update, throws an error if rowHeights.length !== numRows
      ✔ on children update, throws an error if columnWidths.length !== number of <Column>s
      ✔ on columnWidths update, throws an error if columnWidths.length !== number of <Column>s
      ✔ on children update, throws an error if a non-<Column> child is provided
    warnings
      ✔ on numFrozenRows update, should print a warning when numFrozenRows > numRows
      ✔ on numRows update, should print a warning when numFrozenRows > numRows
      ✔ on numFrozenColumns update, should print a warning when numFrozenColumns > num <Column>s
      ✔ on children update, should print a warning when numFrozenColumns > num <Column>s
      ✔ should print a warning when numFrozenColumns > num <Column>s

Copy link
Contributor Author

@cmslewis cmslewis left a comment

Choose a reason for hiding this comment

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

Quick thoughts/questions.

@@ -524,6 +524,8 @@ export class Table extends AbstractComponent<ITableProps, ITableState> {
}

public componentWillReceiveProps(nextProps: ITableProps) {
super.componentWillReceiveProps(nextProps);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

#whoops

Copy link
Contributor

Choose a reason for hiding this comment

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

Womp

// the second part of this conditional will never be true, but it
// informs the TS compiler that we won't be invoking
// childType.prototype on a "string" element.
if (typeof child === "string" || typeof childType === "string") {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Janky, but it was throwing an error before I added the typeof child === "string" check. Turns out typeof childType === undefined for strings, so the check wasn't working as expected before.

Copy link
Contributor

Choose a reason for hiding this comment

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

...so it's a... typeguard? That's... weird. I am confused as to how this could possibly be working. But... ok?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

typeof childType === "string" is the typeguard, yeah. But it never returns true. @giladgray, do you know a way out of this comical quagmire?

} else {
const isColumn = childType.prototype === Column.prototype || Column.prototype.isPrototypeOf(childType);
if (!isColumn) {
console.warn(Errors.TABLE_NON_COLUMN_CHILDREN_WARNING);
throw new Error(Errors.TABLE_NON_COLUMN_CHILDREN_WARNING);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there ever a case where we'd want to let people wrap child <Column>s with a thing? If not, would prefer this to be an Error.

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 you're probably right, @adidahiya might know if the other ever comes up? Not sure.

Copy link
Contributor

Choose a reason for hiding this comment

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

do we really need to validate the react children? what does this buy us? what's the harm in being lenient? please keep #1108 in mind; enforcing constraints on React children in typescript usually gets in the way more often than it helps.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

table.tsx accesses props.children expecting it to be an array of <Column>s (e.g. in Table.getColumnProps). If that accessing fails, the error message is somewhat convoluted; throwing a clear error from validateProps would be more helpful.

Only reservation is that maybe there are cases when people would want to compose a basic <Column> into something else they'd want to provide as a direct child of Table? In that case, a duck-typing approach would seem preferable (i.e. as long as the child in question supports the Column API, we don't care).

I've never heard of anyone doing the above customization, so I'm including the error check as a result of my first point. ^ Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

for now I'm fine with enforcing Column to be a direct child of Table -- as you said we haven't seen the wrapping component use case come up. makes sense to have a better error message and halt execution since the table won't render correctly. let's get to #1108 soon for 2.0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@blueprint-bot
Copy link

Move warnings to bottom

Preview: documentation | table
Coverage: core | datetime

@@ -524,6 +524,8 @@ export class Table extends AbstractComponent<ITableProps, ITableState> {
}

public componentWillReceiveProps(nextProps: ITableProps) {
super.componentWillReceiveProps(nextProps);
Copy link
Contributor

Choose a reason for hiding this comment

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

Womp

// the second part of this conditional will never be true, but it
// informs the TS compiler that we won't be invoking
// childType.prototype on a "string" element.
if (typeof child === "string" || typeof childType === "string") {
Copy link
Contributor

Choose a reason for hiding this comment

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

...so it's a... typeguard? That's... weird. I am confused as to how this could possibly be working. But... ok?

} else {
const isColumn = childType.prototype === Column.prototype || Column.prototype.isPrototypeOf(childType);
if (!isColumn) {
console.warn(Errors.TABLE_NON_COLUMN_CHILDREN_WARNING);
throw new Error(Errors.TABLE_NON_COLUMN_CHILDREN_WARNING);
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 you're probably right, @adidahiya might know if the other ever comes up? Not sure.

@cmslewis
Copy link
Contributor Author

Adding @adidahiya for CR because you've used the Table in the wild.

@cmslewis cmslewis merged commit d553cd2 into master Sep 21, 2017
@cmslewis cmslewis deleted the cl/table-1371-row-heights-length branch September 21, 2017 22:49
@llorca llorca mentioned this pull request Sep 22, 2017
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.

5 participants