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

Try adding a pure Higher Order Component #6313

Merged
merged 3 commits into from
Apr 30, 2018
Merged

Try adding a pure Higher Order Component #6313

merged 3 commits into from
Apr 30, 2018

Conversation

youknowriad
Copy link
Contributor

This PR adds a purify Higher Order Component used to wrap withSelect and withDispatch components (for now) to make them pure.

Pure components mean they don't rerender unless their props or state changed (shallow equality).

Testing instructions

  • Turn the lights on (the "highlight updates" in the React dev tools panel)
  • Compare the lights when typing in a block with master

Notes

  • Using "pure" pattern extensively means we're adding "checks" computation in favor of rendering computation. I think globally it's more performant because we do only shallow comparison but this is something to keep in mind.

@youknowriad youknowriad self-assigned this Apr 20, 2018
@youknowriad youknowriad requested review from mcsf and aduth April 20, 2018 10:41
@youknowriad youknowriad added the [Type] Performance Related to performance efforts label Apr 20, 2018
@atimmer
Copy link
Member

atimmer commented Apr 20, 2018

Would using PureComponent from React be an option?

@youknowriad
Copy link
Contributor Author

@atimmer Not certain what's the difference between the two implementation, do you know more about it?

I kind of like the HoC approach, where you can use it without transforming your component into a class. Also, I don't think it's possible to use React.PureComponent the way I'm implementing purify: A higher component above any class extending wp.element.Component.

@gziolo
Copy link
Member

gziolo commented Apr 20, 2018

From https://reactjs.org/docs/react-api.html#reactpurecomponent:

React.PureComponent’s shouldComponentUpdate() only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only extend PureComponent when you expect to have simple props and state, or use forceUpdate() when you know deep data structures have changed. Or, consider using immutable objects to facilitate fast comparisons of nested data.

Indeed it's interesting how you handle Component class. It doesn't add another wrapper component. Technically you could use PureComponent to wrap functional components, but it probably would end up the same.

Furthermore, React.PureComponent’s shouldComponentUpdate() skips prop updates for the whole component subtree. Make sure all the children components are also “pure”.

Not sure about this part. I would expect children components to re-render when they are wrapped with data HOCs anyway.

@gziolo
Copy link
Member

gziolo commented Apr 20, 2018

@youknowriad can we add some tests once we agree that it is the way to go to make sure that purify never regresses?

@gziolo gziolo added this to the 2.8 milestone Apr 20, 2018
@mcsf
Copy link
Contributor

mcsf commented Apr 20, 2018

Minor: have you considered naming it pure? This follows convention for other HoCs, whose names are not actions, but rather predicates applied to the component:

  • this is a component with safe timeout
  • this is a component [made] pure

@mcsf
Copy link
Contributor

mcsf commented Apr 20, 2018

I generally approve this.

I agree tests, esp. for regressions, are in order. Further: although making some critical components pure should help us a lot, there's the caveat that this HoC may be abused in the future if not applied judiciously (like other kinds of premature or uninformed optimization). Short of improving HoC documentation, I don't really know what the solution for that is. :)

Copy link
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

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

I'd also prefer the name pure.

data/index.js Outdated
@@ -242,7 +242,7 @@ export function dispatch( reducerKey ) {
* @return {Component} Enhanced component with merged state data props.
*/
export const withSelect = ( mapStateToProps ) => createHigherOrderComponent( ( WrappedComponent ) => {
return class ComponentWithSelect extends Component {
Copy link
Member

Choose a reason for hiding this comment

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

Can this be expressed as:

createHigherOrderComponent( compose( [
	purify,
	( WrappedComponent ) => class extends Component {

	}
] ), 'withSelect' );

element/index.js Outdated
*
* @return {WPComponent} Component class with generated display name assigned.
*/
export const purify = createHigherOrderComponent( ( Wrapped ) => {
Copy link
Member

Choose a reason for hiding this comment

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

Why did you choose to export this in element, and not components ?

FWIW, I had a branch with similar change, and encountered that data would then depend on components, which surfaced yet another issue of: Should withSelect and withDispatch be in data or not?

Copy link
Member

Choose a reason for hiding this comment

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

I asked myself several times about withFilters in components which depends on hooks. A very similar case.

Why did you choose to export this in element, and not components ?

I'm sure this is all about dependencies. There needs to be a solution :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I actually didn't think that much about it. I put it under element to match React :) which also provides a pure component definition.

Copy link
Member

Choose a reason for hiding this comment

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

Well explained, I'm sold :)

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, ignoring the fact that React does it, I don't see why, for example, we'd want to export a pure higher-order component in element, but ifCondition higher-order component in components. What judgement do we use to categorize them?

Copy link
Member

Choose a reason for hiding this comment

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

pure, ifCondition, withState, withInstanceId, but also createHigherOrderComponent and compose - we can put all of them together in their own module 💯

Copy link
Member

Choose a reason for hiding this comment

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

The alternative perspective is that components is a baseline, and where we're encountering circular dependencies is a sign where we have our dependencies inversed. Considering viewport and the issues highlighted in #5316, the current dependency hierarchy is:

Viewport (ifViewportMatches) > Components (ifCondition)

(This is problematic in #5316 in introducing the reverse dependency from Popover to Viewport)

Where maybe it ought to be:

Components (ifViewportMatches + ifCondition) > Viewport (withSelect( 'viewport' ) )

This highlights a third type of component: Data-bound components.

We could treat them all separately, e.g. higher-order-components, app-components, ui-components, though it's not clear to me whether this will ultimately solve all of our issues, and of course has added overhead in making determinations of which components / utilities belong where.

Copy link
Member

Choose a reason for hiding this comment

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

So did we just not decide anything here?

Copy link
Member

Choose a reason for hiding this comment

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

I assumed it's going to be renamed to pure.

Copy link
Member

Choose a reason for hiding this comment

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

It was ... I guess you are referring to the location of this HOC, we need to come up with something sooner than later 👍


return class extends Component {
shouldComponentUpdate( nextProps ) {
return ! isShallowEqual( nextProps, this.props );
Copy link
Member

Choose a reason for hiding this comment

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

Interesting conclusion that we don't need to compare state here, and I suppose it makes sense since it's wrapping the original (non-class) component.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed on this one, there is no state in functional components 👍

return <p>{ ++i }</p>;
} );
const wrapper = mount( <MyComp /> );
wrapper.update(); // Upading don't rerender
Copy link
Member

Choose a reason for hiding this comment

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

Typo in the comment (also in line 246).
How this wrapper.update() works, it's super confusing ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well I wanted to rerender the component without passing new props or something. But it's not that important since passing a similar prop is also tested

@gziolo
Copy link
Member

gziolo commented Apr 25, 2018

I would love to see this PR merged later this week. I guess the only remaining issue is where this new functionality should be located as discussed here: #6313 (comment). Otherwise, it has big 👍 from me :)

@aduth
Copy link
Member

aduth commented Apr 25, 2018

I've published @wordpress/is-shallow-equal and will bring it in for use here unless you see reason not to.

Copy link
Contributor

@mcsf mcsf left a comment

Choose a reason for hiding this comment

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

🎸

@youknowriad
Copy link
Contributor Author

@aduth Are you planning to update this PR or use @wordpress/is-shallow-equal in a separate PR? To know whether I should merge or wait :)

@aduth
Copy link
Member

aduth commented Apr 27, 2018

@youknowriad In the process of integrating, I found a bug which led to WordPress/packages#116 . We could merge that and bring it in here, or do separately. I'd be fine to do separately as well.

@youknowriad youknowriad merged commit 1218df9 into master Apr 30, 2018
@youknowriad youknowriad deleted the add/purifyhoc branch April 30, 2018 09:05
@youknowriad youknowriad changed the title Try adding a purify Higher Order Component Try adding a pure Higher Order Component Apr 30, 2018
@youknowriad youknowriad mentioned this pull request Jul 23, 2018
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Performance Related to performance efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants