From 6fba36df84cb1bf0469b1e1effec8824752a0d21 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 Dec 2017 16:29:47 -0500 Subject: [PATCH 1/2] Components: Add withState higher-order component --- components/higher-order/with-state/README.md | 32 +++++++++++++++ components/higher-order/with-state/index.js | 41 +++++++++++++++++++ .../higher-order/with-state/test/index.js | 25 +++++++++++ components/index.js | 1 + 4 files changed, 99 insertions(+) create mode 100644 components/higher-order/with-state/README.md create mode 100644 components/higher-order/with-state/index.js create mode 100644 components/higher-order/with-state/test/index.js diff --git a/components/higher-order/with-state/README.md b/components/higher-order/with-state/README.md new file mode 100644 index 00000000000000..bcc4e704521c54 --- /dev/null +++ b/components/higher-order/with-state/README.md @@ -0,0 +1,32 @@ +withState +========= + +`withState` is a React [higher-order component](https://facebook.github.io/react/docs/higher-order-components.html) which enables a function component to have internal state. + +Wrapping a component with `withState` provides state as props to the wrapped component, along with a `setState` updater function. + +## Usage + +```jsx +/** + * WordPress dependencies + */ +import { withState } from '@wordpress/components'; + +function MyCounter( { count, setState } ) { + return ( + <> + Count: { count } + + + ); +} + +export default withState( { + count: 0, +} )( MyCounter ); +``` + +`withState` optionally accepts an object argument to define the initial state. It returns a function which can then be used in composing your component. diff --git a/components/higher-order/with-state/index.js b/components/higher-order/with-state/index.js new file mode 100644 index 00000000000000..3e2bea08a32da6 --- /dev/null +++ b/components/higher-order/with-state/index.js @@ -0,0 +1,41 @@ +/** + * WordPress dependencies + */ +import { Component, getWrapperDisplayName } from '@wordpress/element'; + +/** + * A Higher Order Component used to provide and manage internal component state + * via props. + * + * @param {?Object} initialState Optional initial state of the component + * @return {Component} Wrapped component + */ +function withState( initialState = {} ) { + return ( OriginalComponent ) => { + class WrappedComponent extends Component { + constructor() { + super( ...arguments ); + + this.setState = this.setState.bind( this ); + + this.state = initialState; + } + + render() { + return ( + + ); + } + } + + WrappedComponent.displayName = getWrapperDisplayName( WrappedComponent, 'state' ); + + return WrappedComponent; + }; +} + +export default withState; diff --git a/components/higher-order/with-state/test/index.js b/components/higher-order/with-state/test/index.js new file mode 100644 index 00000000000000..b9187614a26cbe --- /dev/null +++ b/components/higher-order/with-state/test/index.js @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import { mount } from 'enzyme'; + +/** + * Internal dependencies + */ +import withState from '../'; + +describe( 'withState', () => { + it( 'should pass initial state and allow updates', () => { + const EnhancedComponent = withState( { count: 0 } )( ( { count, setState } ) => ( + + ) ); + + const wrapper = mount( ); + + expect( wrapper.html() ).toBe( '' ); + wrapper.find( 'button' ).simulate( 'click' ); + expect( wrapper.html() ).toBe( '' ); + } ); +} ); diff --git a/components/index.js b/components/index.js index 299c2477f237c1..dc63a3eabae280 100644 --- a/components/index.js +++ b/components/index.js @@ -44,3 +44,4 @@ export { default as withFocusOutside } from './higher-order/with-focus-outside'; export { default as withFocusReturn } from './higher-order/with-focus-return'; export { default as withInstanceId } from './higher-order/with-instance-id'; export { default as withSpokenMessages } from './higher-order/with-spoken-messages'; +export { default as withState } from './higher-order/with-state'; From 925acf910f13d589106ddf9377f3c4d83d6d74f0 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 14 Dec 2017 16:30:11 -0500 Subject: [PATCH 2/2] Blocks: Port HTML block to use withState --- blocks/library/html/index.js | 90 +++++++++++++++--------------------- 1 file changed, 36 insertions(+), 54 deletions(-) diff --git a/blocks/library/html/index.js b/blocks/library/html/index.js index 8f32db2e9a026e..70a20e4a7e7ca6 100644 --- a/blocks/library/html/index.js +++ b/blocks/library/html/index.js @@ -7,7 +7,7 @@ import TextareaAutosize from 'react-autosize-textarea'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; +import { withState } from '@wordpress/components'; /** * Internal dependencies @@ -41,60 +41,42 @@ registerBlockType( 'core/html', { }, }, - edit: class extends Component { - constructor() { - super( ...arguments ); - this.preview = this.preview.bind( this ); - this.edit = this.edit.bind( this ); - this.state = { - preview: false, - }; - } - - preview() { - this.setState( { preview: true } ); - } - - edit() { - this.setState( { preview: false } ); - } - - render() { - const { preview } = this.state; - const { attributes, setAttributes, focus } = this.props; - - return ( -
- { focus && - -
- - -
-
- } - { preview ? -
: - setAttributes( { content: event.target.value } ) } - /> - } - { focus && - - -

{ __( 'Add custom HTML code and preview it right here in the editor.' ) }

-
-
- } + edit: withState( { + preview: false, + } )( ( { attributes, setAttributes, setState, focus, preview } ) => [ + focus && ( + +
+ +
- ); - } - }, +
+ ), + preview ? +
: + setAttributes( { content: event.target.value } ) } + />, + focus && ( + + +

{ __( 'Add custom HTML code and preview it right here in the editor.' ) }

+
+
+ ), + ] ), save( { attributes } ) { return attributes.content;