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

Components: Add withState higher-order component #4016

Merged
merged 2 commits into from
Dec 15, 2017
Merged

Conversation

aduth
Copy link
Member

@aduth aduth commented Dec 14, 2017

Supersedes #3435

This pull request seeks to add a new withState higher-order component to enable ES5 block implementers to introduce state to their components without reaching for the Component class.

View documentation

Included is an example porting the HTML block component to a stateless function component using withState.

Testing instructions:

Verify that there are no regressions in the behavior of toggling between Preview and HTML in the HTML block.

@aduth aduth added [Feature] Blocks Overall functionality of blocks [Feature] UI Components Impacts or related to the UI component system labels Dec 14, 2017
}
edit: withState( {
preview: false,
} )( ( { attributes, setAttributes, setState, focus, preview } ) => [
Copy link
Member

@gziolo gziolo Dec 15, 2017

Choose a reason for hiding this comment

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

Should we start using React.Fragment to avoid using keys? Do we want to update Babel first? :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Should we start using React.Fragment to avoid using keys? Do we want to update Babel first? :)

Yeah, I'd probably want to avoid the direct reference to React.Fragment, but if we had <> syntax available, I'd be open to using it. Maybe in a subsequent pull request where we upgrade Babel?

Copy link
Member

Choose a reason for hiding this comment

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

Sure, I can spin up PR with Babel upgrade on Monday :)

return (
<>
Count: { count }
<button onClick={ () => setState( { count: count + 1 } ) }>
Copy link
Member

@gziolo gziolo Dec 15, 2017

Choose a reason for hiding this comment

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

Just noting that React docs discourage using setState( { count: count + 1 } ) this way when it depends on props:

React may batch multiple setState() calls into a single update for performance.

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

They suggest using the following:

setState( count => ( { count: count + 1 } )

See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous. I think we should follow those guidelines in here, and in the unit test.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we should follow those guidelines in here, and in the unit test.

Updated in rebased 6fba36d.

}
}

WrappedComponent.displayName = getWrapperDisplayName( WrappedComponent, 'withState' );
Copy link
Member

Choose a reason for hiding this comment

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

In other places, I omitted with prefix for the display name. We should either update all existing getWrapperDisplayName calls or put state here instead. Both work for me.

Copy link
Member Author

Choose a reason for hiding this comment

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

We should either update all existing getWrapperDisplayName calls or put state here instead.

Updated in rebased 6fba36d.

Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

I like this HOC a lot. This is exactly what we need to make it possible to use state with functional components or when using ES5 only. Great work 💯

I left some minor comments. They aren't blockers, but taking into account that we are updating docs we might want to be super strict and align with React docs :)

@lamosty
Copy link
Member

lamosty commented Dec 15, 2017

This is great, thanks for doing it!

@youknowriad
Copy link
Contributor

Do we really want to add an maintain this kind of API? Do we really want to optimize for the ES5 use-case?

  • If we want a similar API, should we refer to recompose in the docs for example?
  • If we want friendly ES5 components, should we refer to create-react-class instead (or expose it). If this PR is meant to address the ES5 issue, it only does so for the state, what if someone wants to use the lifecycle methods?

@gziolo
Copy link
Member

gziolo commented Dec 18, 2017

I would advocate for full support for ES5 style as long as it is the only way to avoid build step.

If we want a similar API, should we refer to recompose in the docs for example?

We might advertise recompose library as a good way to deep diver into HOC world.

If we want friendly ES5 components, should we refer to create-react-class instead (or expose it). If this PR is meant to address the ES5 issue, it only does so for the state, what if someone wants to use the lifecycle methods?

create-react-class isn't fully compatible with ES6 class component. Personally, I would offer withLifecycleMethods HOC instead to make it possible to mirror the original React 16 behavior. Actually I will ask on Twitter what React core team thinks about such idea and if there are any potential drawbacks.

@aduth
Copy link
Member Author

aduth commented Dec 18, 2017

Do we really want to optimize for the ES5 use-case?

Personally I think we should seek to be as inclusive as possible to those who choose not to include a build step. Also, arguably one of the points of this change was that it's an obvious pattern for introducing state even for those who choose to use newer language syntax.

should we refer to recompose in the docs for example?

Recompose includes a fair bit more functionality that may be overwhelming for those who are simply trying to maintain some local component state. It's hard to draw a line between what we do and don't want to support and maintain ourselves. I think something like this covers a pretty good baseline option without needing to reach for more powerful solutions.

If we want friendly ES5 components, should we refer to create-react-class instead (or expose it).

As mentioned in #3435, I don't think we should encourage create-react-class. From what I can tell, it exists largely for backwards-compatibility with React.createClass, including some now-defunct "surprises" like auto-binding.

it only does so for the state, what if someone wants to use the lifecycle methods?

We could introduce a HoC for lifecycle, but instead I'd be curious to identify the use-cases that would call for lifecycle in block implementations and see if we can address those with more targeted utilities.

@gziolo
Copy link
Member

gziolo commented Dec 18, 2017

We could introduce a HoC for lifecycle, but instead I'd be curious to identify the use-cases that would call for lifecycle in block implementations and see if we can address those with more targeted utilities.

It might make sense to have withEventHandlers which would bind/unbind event handlers. We would need to go through the existing code and identify what is really necessary. It wouldn't be a bad idea to convert a few existing blocks to use such HOCs instead of Component.

@mtias
Copy link
Member

mtias commented Dec 26, 2017

Thanks for working on this. I do like this API and would like to see how we could use it for our own components as well, as it seems inherently more flexible in not tying to a library but tying to WP patterns instead.

@gziolo
Copy link
Member

gziolo commented Jan 8, 2018

I asked on Twitter the following:

If you could shape React’s public API again, would you use expose class syntax for the component or rather use functional approach with higher order functions that add state support or lifecycle methods through composition?

Replay from Dan Abramov:

See my reply to a similar question in https://dev.to/dan_abramov/comment/1n29

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Blocks Overall functionality of blocks [Feature] UI Components Impacts or related to the UI component system
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants