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

React 16 and Enzyme 3 migration #1885

Merged
merged 43 commits into from
Dec 13, 2017
Merged

React 16 and Enzyme 3 migration #1885

merged 43 commits into from
Dec 13, 2017

Conversation

adidahiya
Copy link
Contributor

@adidahiya adidahiya commented Dec 11, 2017

Fixes #1051
Fixes #144
Fixes #866 (for real)
Fixes #1860
Fixes #1680

Changes in this PR

  • Upgrade react and react-dom to v16
    • Load es6-shim in all tests to ensure compatibility
    • Fix prop validation tests that broke due to React failing faster on thrown JS Errors
  • Replace react-addons-css-transition-group with react-transition-group
  • Replace pure-render-decorator usage with extends React.PureComponent
    • Split AbstractComponent into impure AbstractComponent and pure AbstractPureComponent classes
  • Upgrade enzyme to v3 and install enzyme-adapter-react-16
    • Fix various tests that broke due to Enzyme API changes
  • <Portal>: Replace ReactDOM._unstableRenderSubtreeToContainer with ReactDOM.createPortal

Copy link
Contributor Author

@adidahiya adidahiya left a comment

Choose a reason for hiding this comment

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

make sure to check out the lockfile

import { CSSTransition } from "react-transition-group";
import { IOverlayProps } from "./overlay";

export function OverlayTransition<
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is no longer used; will remove

transitionLeaveTimeout={transitionDuration}
transitionName={transitionName}
>
<TransitionGroup appear={true}>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -38,8 +37,7 @@ export interface IButtonGroupProps extends IProps, React.HTMLProps<HTMLDivElemen

// this component is simple enough that tests would be purely tautological.
/* istanbul ignore next */
@PureRender
export class ButtonGroup extends React.Component<IButtonGroupProps, {}> {
export class ButtonGroup extends React.PureComponent<IButtonGroupProps, {}> {
Copy link
Contributor

Choose a reason for hiding this comment

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

we should be able to drop these empty {} state types, as the React types now provide a default: interface Component<P = {}, S = {}>.

though it's fine to not do this now, or ever.

@@ -31,8 +30,7 @@ export interface IIconProps extends IIntentProps, IProps {
iconSize?: 16 | 20 | "inherit";
}

@PureRender
export class Icon extends React.Component<IIconProps & React.HTMLAttributes<HTMLSpanElement>, never> {
export class Icon extends React.PureComponent<IIconProps & React.HTMLAttributes<HTMLSpanElement>, never> {
Copy link
Contributor

Choose a reason for hiding this comment

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

remove never, or make it {} like all the others

onMouseDown={this.handleBackdropMouseDown}
tabIndex={this.props.canOutsideClickClose ? 0 : null}
/>
<CSSTransition classNames={transitionName} timeout={transitionDuration}>
Copy link
Contributor

Choose a reason for hiding this comment

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

classNames 😱
should we do this??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that's their API. are you asking if we should do this for our own props? I don't think there's anything wrong with the current names, so would prefer to keep them

constructor(props: IPortalProps, context: IPortalContext) {
super(props, context);
this.targetElement = document.createElement("div");
this.targetElement.classList.add(Classes.PORTAL);
Copy link
Contributor

Choose a reason for hiding this comment

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

why move to constructor instead of didMount?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it happens sooner this way. also this is how the API example was set up

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh also I need it to be there by the time render() is called, which is not true of componentDidMount

@blueprint-bot
Copy link

Address follow up comments on #1898

Preview: documentation | landing | table

@blueprint-bot
Copy link

Remove extraneous core karma test config

Preview: documentation | landing | table

@adidahiya
Copy link
Contributor Author

Some regressions I noticed (which can be addressed in follow-up PRs):

  • ESC key to close Dialogs doesn't work until you hit TAB to move focus into the Dialog element
  • When opening a <Select>, the input field is not focussed immediately (likely related to the above issue)

@adidahiya adidahiya requested review from themadcreator and removed request for jkillian December 13, 2017 06:56
@adidahiya
Copy link
Contributor Author

Filed #1901 to address those later ^

Copy link
Contributor

@themadcreator themadcreator left a comment

Choose a reason for hiding this comment

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

Questions. Otherwise good to go

"src/accessibility/*",
"src/common/abstractComponent*",
// deprecated components
"src/components/popover/*",
Copy link
Contributor

Choose a reason for hiding this comment

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

are these actually deprecated if they're being replaced by labs components?

Copy link
Contributor Author

@adidahiya adidahiya Dec 13, 2017

Choose a reason for hiding this comment

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

we'll need to remember to remove this line when @cmslewis's Popover2 -> Popover change goes in.

);
}

public componentDidMount() {
Copy link
Contributor

@themadcreator themadcreator Dec 13, 2017

Choose a reason for hiding this comment

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

it looks like componentDidMount is maintained, but componentDidUpdate was removed which used an unstable method. Is this functionality still captured?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah so now that we're actually returning something from render instead of null all the time, the React tree works as expected. there's a unit test for the context-preserving functionality (it("respects blueprintPortalClassName on context")

@@ -2,4 +2,11 @@
* Copyright 2017 Palantir Technologies, Inc. All rights reserved.
*/

import "es6-shim";
Copy link
Contributor

Choose a reason for hiding this comment

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

wasn't this supposed to be removed?

Copy link
Contributor

Choose a reason for hiding this comment

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

(though it's just test code code, so... yolo)

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, you explicitly need to bring it in to support React 16. We're definitely not removing es6-shim from the page runtime; we are pushing the responsibility down to consumers of the library (e.g. the test environment, table-dev-app, docs-app, our other apps, etc.)

Copy link
Contributor

Choose a reason for hiding this comment

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

oh i see

@@ -492,7 +492,7 @@ export class Table extends AbstractComponent<ITableProps, ITableState> {
// time. it serves as a signal that we can switch to batch rendering.
private didCompletelyMount = false;

public constructor(props: ITableProps, context?: any) {
public constructor(props: ITableProps & { children: React.ReactNode }, context?: any) {
Copy link
Contributor

Choose a reason for hiding this comment

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

ITableProps already has a definition for children, shouldn't these match?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah good catch. will fix

@@ -664,7 +664,8 @@ export class Table extends AbstractComponent<ITableProps, ITableState> {
);
}

public componentWillReceiveProps(nextProps: ITableProps) {
public componentWillReceiveProps(nextProps: ITableProps & { children: React.ReactNode }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

^ same

@blueprint-bot
Copy link

Remove extra props interface in table impl

Preview: documentation | landing | table

Copy link
Contributor

@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.

Requesting changes for the test:karma:debug thing in particular.

public componentWillUnmount() {
ReactDOM.unmountComponentAtNode(this.targetElement);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't we need this anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

React cleans up itself when you actually return things in render

@@ -39,8 +38,7 @@ export interface ITab2Props extends IProps {
title?: string | JSX.Element;
}

@PureRender
export class Tab2 extends React.Component<ITab2Props, {}> {
export class Tab2 extends React.PureComponent<ITab2Props, {}> {
Copy link
Contributor

Choose a reason for hiding this comment

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

@giladgray FYI: this edit. (You moved these files in #1900.)

@@ -22,7 +22,7 @@ import {
describe("<CollapsibleList>", () => {
it("adds className to itself", () => {
const list = renderCollapsibleList(3, { className: "winner" });
assert.lengthOf(list.find(".winner"), 1);
assert.isNotEmpty(list.find(".winner"));
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, this check isn't equivalent. Was this suffering from Enzyme 3's .hostNodes() issue (enzymejs/enzyme#1174)? Seems like the fix for too many elements here is:

assert.lengthOf(list.find(".winner").hostNodes(), 1);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah but is that really a more meaningful / useful test? IMO this is another tautology and I would rather have a auto-generated test suite for spreading props / classNames rather than including one of these test cases at the start of every component test suite

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok I can switch to hostNodes

@@ -15,7 +15,7 @@ describe("<Text>", () => {
const textContent = "textContent";
const className = "bp-test-class";
const wrapper = mount(<Text className={className}>{textContent}</Text>);
const element = wrapper.find(`.${className}`);
const element = wrapper.find(`div.${className}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

wrapper.find(`.${className}`).hostNodes()

is clearer

@@ -130,10 +128,11 @@ describe("Toaster", () => {

it("focuses on newly created toast", done => {
toaster.show({ message: "focus on me" });
// small explicity timeout reduces flakiness of these tests
Copy link
Contributor

Choose a reason for hiding this comment

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

explicitly

@@ -24,6 +24,7 @@
"test": "npm-run-all -s test:pre -p test:karma",
"test:pre": "tsc -p ./test",
"test:karma": "karma start",
"test:karma:debug": "karma start --browsers=Chrome --single-run=false --reporters=mocha --debug",
Copy link
Contributor

Choose a reason for hiding this comment

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

Same

@@ -24,26 +24,28 @@
"test": "npm-run-all -s compile:cjs test:pre -p test:karma test:iso",
"test:pre": "tsc -p ./test",
"test:karma": "karma start",
"test:karma:debug": "karma start --browsers=Chrome --single-run=false --reporters=mocha --debug",
Copy link
Contributor

Choose a reason for hiding this comment

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

Same

@@ -73,7 +73,7 @@ describe("Suggest", () => {

it("scrolls active item into view when popover opens", () => {
const wrapper = suggest();
const queryList = (wrapper.getNode() as any).queryList; // private ref
const queryList = ((wrapper.instance() as Suggest<Film>) as any).queryList; // private ref
Copy link
Contributor

Choose a reason for hiding this comment

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

what does double-casting do for us?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just helps clarify intent to developers

@@ -24,27 +24,29 @@
"test": "npm-run-all -s compile:cjs test:pre -p test:karma test:iso",
"test:pre": "tsc -p ./test",
"test:karma": "karma start",
"test:karma:debug": "karma start --browsers=Chrome --single-run=false --reporters=mocha --debug",
Copy link
Contributor

Choose a reason for hiding this comment

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

Same

@@ -492,7 +492,7 @@ export class Table extends AbstractComponent<ITableProps, ITableState> {
// time. it serves as a signal that we can switch to batch rendering.
private didCompletelyMount = false;

public constructor(props: ITableProps, context?: any) {
public constructor(props: ITableProps & { children: React.ReactNode }, context?: any) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Worth making a new props interface in core/.../props.ts? This is used in a few places.

e.g.

export interface IChildrenProps {
    children: React.ReactNode;
}

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, I removed it

@blueprint-bot
Copy link

Address CR feedback on tests

Preview: documentation | landing | table

@adidahiya adidahiya merged commit 0376104 into master Dec 13, 2017
@adidahiya adidahiya deleted the ad/react-16 branch December 13, 2017 23:07
@hysoftwareeng
Copy link

hysoftwareeng commented Dec 15, 2017

Is this going to be in the next release (after 1.35.0)? I currently still see an unmet peer dependency when installing core 1.35.0 for react-addons-css-transition-group
capture

@giladgray
Copy link
Contributor

@hysoftwareeng this will be in our upcoming 2.0 release, but the installation instructions do not change: react is still a peerDependency so you must explicitly install it yourself in your application.

screen shot 2017-12-15 at 11 08 53 am

@skovy
Copy link
Contributor

skovy commented Jan 17, 2018

When will a release be cut for 2.0? It's not possible to upgrade to React 16 if blueprintjs is a dependency. Thanks!

@adidahiya
Copy link
Contributor Author

It's not possible to upgrade to React 16 if blueprintjs is a dependency.

What are the specific errors you run into? React 16 is supported as a peer dependency: #1660

@rmendelovits
Copy link

rmendelovits commented Jan 17, 2018

I have the following error when trying to upgrade to React 16:


npm WARN pure-render-decorator@1.2.1 requires a peer of react@^15.0.0 || ^0.14.0
 but none is installed. You must install peer dependencies yourself.
npm WARN react-addons-css-transition-group@15.6.2 requires a peer of react@^15.4
.2 but none is installed. You must install peer dependencies yourself.

But where does pure-render-decorator/react-addons-css-transition-group come from?

the pure-render-decorator is a requirement for these two packages.
@blueprintjs/core
@blueprintjs/table

and I get this warning when I try to move to react-transition-group

npm WARN @blueprintjs/core@1.35.1 requires a peer of react-addons-css-transition
-group@^15.0.1 || ^0.14 but none is installed. You must install peer dependencie
s yourself.

@adidahiya
Copy link
Contributor Author

Those warnings can be safely ignored. See #866 (comment).

Anyway, I just released the first beta of blueprint 2.0.0, you can try it out for react 16 support. Release notes coming soon.

@rmendelovits
Copy link

Thanks

@skovy
Copy link
Contributor

skovy commented Jan 18, 2018

@adidahiya not sure I follow. react-addons-css-transition-group requires react@^15.4.2, and we have react@16.2.0. Does that mean react-addons-css-transition-group would then require us to have two versions of React in the bundle?

When I upgrade to React 16.2.0 and run yarn check, I'm recieve these errors:

error "react-addons-css-transition-group#react@^15.4.2" doesn't satisfy found match of "react@16.2.0"
error "@blueprintjs/core#pure-render-decorator#react@^15.0.0 \|\| ^0.14.0" doesn't satisfy found match of "react@16.2.0"
error "@blueprintjs/datetime#react-day-picker#react@~0.13.x \|\| ~0.14.x \|\| ^15.0.0" doesn't satisfy found match of "react@16.2.0"

@adidahiya
Copy link
Contributor Author

@skovy Since these are peer dependencies, no, you don't need to end up with two versions of React in your bundle. Warnings aside, does this cause a problem at runtime in your application when you try to use React 16.2.0?

Also, I suggest upgrading react-day-picker to a version that allows React 16 as a peer dependency: https://github.com/gpbl/react-day-picker/blob/3156e3de1664caa15cf12cfd0fdd96350874e8c5/package.json#L55

@skovy
Copy link
Contributor

skovy commented Jan 21, 2018

@adidahiya got it, thanks for the insight and I appreciate the help! 👍

@skovy
Copy link
Contributor

skovy commented Jan 30, 2018

@adidahiya I opened a PR to upgrade react-day-picker, but ran into linting problems. Would appreciate any insights on #2055.

@giladgray giladgray mentioned this pull request Feb 9, 2018
3 tasks
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.

9 participants