diff --git a/plugins/woocommerce-admin/packages/components/CHANGELOG.md b/plugins/woocommerce-admin/packages/components/CHANGELOG.md
index a40912c67aa..98dc719f121 100644
--- a/plugins/woocommerce-admin/packages/components/CHANGELOG.md
+++ b/plugins/woocommerce-admin/packages/components/CHANGELOG.md
@@ -1,5 +1,7 @@
# Unreleased
+- Replace deprecated wp.compose.withState with wp.element.useState. #8338
+
# 9.0.0
- Update line-height of SelectControl label to avoid truncated descenders in some typefaces and zoom levels. #8186
diff --git a/plugins/woocommerce-admin/packages/components/src/calendar/stories/date-picker.js b/plugins/woocommerce-admin/packages/components/src/calendar/stories/date-picker.js
index 97a70ec7099..c40a28ca10b 100644
--- a/plugins/woocommerce-admin/packages/components/src/calendar/stories/date-picker.js
+++ b/plugins/woocommerce-admin/packages/components/src/calendar/stories/date-picker.js
@@ -2,23 +2,25 @@
* External dependencies
*/
import moment from 'moment';
-import { withState } from '@wordpress/compose';
import { DatePicker, H, Section } from '@woocommerce/components';
-import { createElement } from '@wordpress/element';
-
+import { useState } from '@wordpress/element';
const dateFormat = 'MM/DD/YYYY';
-const DatePickerExample = withState( {
- after: null,
- afterText: '',
- before: null,
- beforeText: '',
- afterError: null,
- beforeError: null,
- focusedInput: 'startDate',
-} )( ( { after, afterText, afterError, setState } ) => {
+const DatePickerExample = () => {
+ const [ state, setState ] = useState( {
+ after: null,
+ afterText: '',
+ before: null,
+ beforeText: '',
+ afterError: null,
+ beforeError: null,
+ focusedInput: 'startDate',
+ } );
+ const { after, afterText, afterError } = state;
+
function onDatePickerUpdate( { date, text, error } ) {
setState( {
+ ...state,
after: date,
afterText: text,
afterError: error,
@@ -40,7 +42,7 @@ const DatePickerExample = withState( {
);
-} );
+};
export const Basic = () => ;
diff --git a/plugins/woocommerce-admin/packages/components/src/calendar/stories/date-range.js b/plugins/woocommerce-admin/packages/components/src/calendar/stories/date-range.js
index 322a10e0c8f..75be254edbf 100644
--- a/plugins/woocommerce-admin/packages/components/src/calendar/stories/date-range.js
+++ b/plugins/woocommerce-admin/packages/components/src/calendar/stories/date-range.js
@@ -2,23 +2,29 @@
* External dependencies
*/
import moment from 'moment';
-import { withState } from '@wordpress/compose';
import { DateRange, H, Section } from '@woocommerce/components';
-import { createElement, Fragment } from '@wordpress/element';
+import { useState } from '@wordpress/element';
const dateFormat = 'MM/DD/YYYY';
-const DateRangeExample = withState( {
- after: null,
- afterText: '',
- before: null,
- beforeText: '',
- afterError: null,
- beforeError: null,
- focusedInput: 'startDate',
-} )( ( { after, afterText, before, beforeText, focusedInput, setState } ) => {
+const DateRangeExample = () => {
+ const [ state, setState ] = useState( {
+ after: null,
+ afterText: '',
+ before: null,
+ beforeText: '',
+ afterError: null,
+ beforeError: null,
+ focusedInput: 'startDate',
+ } );
+
+ const { after, afterText, before, beforeText, focusedInput } = state;
+
function onRangeUpdate( update ) {
- setState( update );
+ setState( {
+ ...state,
+ ...update,
+ } );
}
return (
@@ -40,7 +46,7 @@ const DateRangeExample = withState( {
>
);
-} );
+};
export const Basic = () => ;
diff --git a/plugins/woocommerce-admin/packages/components/src/ellipsis-menu/stories/index.js b/plugins/woocommerce-admin/packages/components/src/ellipsis-menu/stories/index.js
index 5a903ba918a..fc355a37e9e 100644
--- a/plugins/woocommerce-admin/packages/components/src/ellipsis-menu/stories/index.js
+++ b/plugins/woocommerce-admin/packages/components/src/ellipsis-menu/stories/index.js
@@ -1,47 +1,57 @@
/**
* External dependencies
*/
-import { withState } from '@wordpress/compose';
-import { Fragment } from '@wordpress/element';
+import { Fragment, useState } from '@wordpress/element';
import { Icon } from '@wordpress/icons';
import CrossSmall from 'gridicons/dist/cross-small';
import { EllipsisMenu, MenuItem, MenuTitle } from '@woocommerce/components';
-const ExampleEllipsisMenu = withState( {
- showCustomers: true,
- showOrders: true,
-} )( ( { setState, showCustomers, showOrders } ) => (
- (
-
- Display stats
-
-
-
-
- ) }
- />
-) );
+const ExampleEllipsisMenu = () => {
+ const [ { showCustomers, showOrders }, setState ] = useState( {
+ showCustomers: true,
+ showOrders: true,
+ } );
+ return (
+ (
+
+ Display stats
+
+
+
+
+ ) }
+ />
+ );
+};
export const Basic = () => ;
diff --git a/plugins/woocommerce-admin/packages/components/src/image-upload/stories/index.js b/plugins/woocommerce-admin/packages/components/src/image-upload/stories/index.js
index 469dd33e0ca..9226a3e78cd 100644
--- a/plugins/woocommerce-admin/packages/components/src/image-upload/stories/index.js
+++ b/plugins/woocommerce-admin/packages/components/src/image-upload/stories/index.js
@@ -1,17 +1,19 @@
/**
* External dependencies
*/
-import { withState } from '@wordpress/compose';
+import { useState } from '@wordpress/element';
import { ImageUpload } from '@woocommerce/components';
-const ImageUploadExample = withState( {
- image: null,
-} )( ( { setState, logo } ) => (
- setState( { logo: image } ) }
- />
-) );
+const ImageUploadExample = () => {
+ const [ image, setImage ] = useState( null );
+
+ return (
+ setImage( _image ) }
+ />
+ );
+};
export const Basic = () => ;
diff --git a/plugins/woocommerce-admin/packages/components/src/search-list-control/index.js b/plugins/woocommerce-admin/packages/components/src/search-list-control/index.js
index 085f99f088a..de7d91fc3d2 100644
--- a/plugins/woocommerce-admin/packages/components/src/search-list-control/index.js
+++ b/plugins/woocommerce-admin/packages/components/src/search-list-control/index.js
@@ -8,8 +8,13 @@ import {
TextControl,
withSpokenMessages,
} from '@wordpress/components';
-import { createElement, Component, Fragment } from '@wordpress/element';
-import { compose, withInstanceId, withState } from '@wordpress/compose';
+import {
+ createElement,
+ Fragment,
+ useState,
+ useEffect,
+} from '@wordpress/element';
+import { compose, withInstanceId } from '@wordpress/compose';
import { escapeRegExp, findIndex } from 'lodash';
import NoticeOutlineIcon from 'gridicons/dist/notice-outline';
import PropTypes from 'prop-types';
@@ -43,28 +48,33 @@ const defaultMessages = {
/**
* Component to display a searchable, selectable list of items.
+ *
+ * @param {Object} props
*/
-export class SearchListControl extends Component {
- constructor() {
- super( ...arguments );
+export const SearchListControl = ( props ) => {
+ const [ searchValue, setSearchValue ] = useState( props.search || '' );
+ const {
+ isSingle,
+ isLoading,
+ onChange,
+ selected,
+ instanceId,
+ messages: propsMessages,
+ isCompact,
+ debouncedSpeak,
+ onSearch,
+ className = '',
+ } = props;
- this.onSelect = this.onSelect.bind( this );
- this.onRemove = this.onRemove.bind( this );
- this.onClear = this.onClear.bind( this );
- this.isSelected = this.isSelected.bind( this );
- this.defaultRenderItem = this.defaultRenderItem.bind( this );
- this.renderList = this.renderList.bind( this );
- }
+ const messages = { ...defaultMessages, ...propsMessages };
- componentDidUpdate( prevProps ) {
- const { onSearch, search } = this.props;
- if ( search !== prevProps.search && typeof onSearch === 'function' ) {
- onSearch( search );
+ useEffect( () => {
+ if ( typeof onSearch === 'function' ) {
+ onSearch( searchValue );
}
- }
+ }, [ onSearch, searchValue ] );
- onRemove( id ) {
- const { isSingle, onChange, selected } = this.props;
+ const onRemove = ( id ) => {
return () => {
if ( isSingle ) {
onChange( [] );
@@ -75,13 +85,12 @@ export class SearchListControl extends Component {
...selected.slice( i + 1 ),
] );
};
- }
+ };
- onSelect( item ) {
- const { isSingle, onChange, selected } = this.props;
+ const onSelect = ( item ) => {
return () => {
- if ( this.isSelected( item ) ) {
- this.onRemove( item.id )();
+ if ( isSelected( item ) ) {
+ onRemove( item.id )();
return;
}
if ( isSingle ) {
@@ -90,39 +99,32 @@ export class SearchListControl extends Component {
onChange( [ ...selected, item ] );
}
};
- }
+ };
- onClear() {
- this.props.onChange( [] );
- }
+ const isSelected = ( item ) =>
+ findIndex( selected, { id: item.id } ) !== -1;
- isSelected( item ) {
- return findIndex( this.props.selected, { id: item.id } ) !== -1;
- }
-
- getFilteredList( list, search ) {
- const { isHierarchical } = this.props;
+ const getFilteredList = ( list, search ) => {
+ const { isHierarchical } = props;
if ( ! search ) {
return isHierarchical ? buildTermsTree( list ) : list;
}
- const messages = { ...defaultMessages, ...this.props.messages };
const re = new RegExp( escapeRegExp( search ), 'i' );
- this.props.debouncedSpeak( messages.updated );
+ debouncedSpeak( messages.updated );
const filteredList = list
.map( ( item ) => ( re.test( item.name ) ? item : false ) )
.filter( Boolean );
return isHierarchical
? buildTermsTree( filteredList, list )
: filteredList;
- }
+ };
- defaultRenderItem( args ) {
+ const defaultRenderItem = ( args ) => {
return ;
- }
+ };
- renderList( list, depth = 0 ) {
- const { isSingle, search, instanceId } = this.props;
- const renderItem = this.props.renderItem || this.defaultRenderItem;
+ const renderList = ( list, depth = 0 ) => {
+ const renderItem = props.renderItem || defaultRenderItem;
if ( ! list ) {
return null;
}
@@ -132,23 +134,20 @@ export class SearchListControl extends Component {
{ renderItem( {
item,
- isSelected: this.isSelected( item ),
- onSelect: this.onSelect,
+ isSelected: isSelected( item ),
+ onSelect,
isSingle,
- search,
+ search: searchValue,
depth,
controlId: instanceId,
} ) }
- { this.renderList( item.children, depth + 1 ) }
+ { renderList( item.children, depth + 1 ) }
) );
- }
-
- renderListSection() {
- const { isLoading, search } = this.props;
- const messages = { ...defaultMessages, ...this.props.messages };
+ };
+ const renderListSection = () => {
if ( isLoading ) {
return (
@@ -156,7 +155,7 @@ export class SearchListControl extends Component {
);
}
- const list = this.getFilteredList( this.props.list, search );
+ const list = getFilteredList( props.list, searchValue );
if ( ! list.length ) {
return (
@@ -169,9 +168,9 @@ export class SearchListControl extends Component {
/>
- { search
+ { searchValue
? // eslint-disable-next-line @wordpress/valid-sprintf
- sprintf( messages.noResults, search )
+ sprintf( messages.noResults, searchValue )
: messages.noItems }
@@ -180,15 +179,12 @@ export class SearchListControl extends Component {
return (
- { this.renderList( list ) }
+ { renderList( list ) }
);
- }
-
- renderSelectedSection() {
- const { isLoading, isSingle, selected } = this.props;
- const messages = { ...defaultMessages, ...this.props.messages };
+ };
+ const renderSelectedSection = () => {
if ( isLoading || isSingle || ! selected ) {
return null;
}
@@ -202,7 +198,7 @@ export class SearchListControl extends Component {
} />
+ );
+ expect( queryByText( 'This is a popover' ) ).toBeNull();
+ } );
+
+ test( 'Show popoverContents after clicking the button', () => {
+ const { queryByText, queryByRole } = render(
+ This is a popover }
+ />
+ );
+
+ fireEvent.click(
+ queryByRole( 'button', { id: 'woocommerce-tag__label-1' } )
+ );
+ expect( queryByText( 'This is a popover' ) ).toBeDefined();
+ } );
} );
diff --git a/plugins/woocommerce-admin/packages/components/src/text-control-with-affixes/stories/index.js b/plugins/woocommerce-admin/packages/components/src/text-control-with-affixes/stories/index.js
index bb855ff8e21..d34cb806ded 100644
--- a/plugins/woocommerce-admin/packages/components/src/text-control-with-affixes/stories/index.js
+++ b/plugins/woocommerce-admin/packages/components/src/text-control-with-affixes/stories/index.js
@@ -2,91 +2,95 @@
* External dependencies
*/
import { TextControlWithAffixes } from '@woocommerce/components';
+import { useState } from '@wordpress/element';
-/**
- * External dependencies
- */
-import { withState } from '@wordpress/compose';
+const Examples = () => {
+ const [ state, setState ] = useState( {
+ first: '',
+ second: '',
+ third: '',
+ fourth: '',
+ fifth: '',
+ } );
+ const { first, second, third, fourth, fifth } = state;
+ const partialUpdate = ( partial ) => {
+ setState( { ...state, ...partial } );
+ };
-const Examples = withState( {
- first: '',
- second: '',
- third: '',
- fourth: '',
- fifth: '',
-} )( ( { first, second, third, fourth, fifth, setState } ) => (
-
- setState( { first: value } ) }
- />
- setState( { first: value } ) }
- disabled
- />
- setState( { second: value } ) }
- />
- setState( { second: value } ) }
- disabled
- />
- setState( { third: value } ) }
- />
- setState( { third: value } ) }
- disabled
- />
- setState( { fourth: value } ) }
- />
- setState( { fourth: value } ) }
- disabled
- />
- setState( { fifth: value } ) }
- help="This is some help text."
- />
- setState( { fifth: value } ) }
- help="This is some help text."
- disabled
- />
-
-) );
+ return (
+
+ partialUpdate( { first: value } ) }
+ />
+ partialUpdate( { first: value } ) }
+ disabled
+ />
+ partialUpdate( { second: value } ) }
+ />
+ partialUpdate( { second: value } ) }
+ disabled
+ />
+ partialUpdate( { third: value } ) }
+ />
+ partialUpdate( { third: value } ) }
+ disabled
+ />
+ partialUpdate( { fourth: value } ) }
+ />
+ partialUpdate( { fourth: value } ) }
+ disabled
+ />
+ partialUpdate( { fifth: value } ) }
+ help="This is some help text."
+ />
+ partialUpdate( { fifth: value } ) }
+ help="This is some help text."
+ disabled
+ />
+
+ );
+};
export const Basic = () => ;
diff --git a/plugins/woocommerce-admin/packages/components/src/text-control/stories/index.js b/plugins/woocommerce-admin/packages/components/src/text-control/stories/index.js
index 5ef4f1a59ce..97febc829d0 100644
--- a/plugins/woocommerce-admin/packages/components/src/text-control/stories/index.js
+++ b/plugins/woocommerce-admin/packages/components/src/text-control/stories/index.js
@@ -2,29 +2,24 @@
* External dependencies
*/
import { TextControl } from '@woocommerce/components';
-import { createElement } from '@wordpress/element';
+import { useState } from '@wordpress/element';
-/**
- * External dependencies
- */
-import { withState } from '@wordpress/compose';
+const Example = () => {
+ const [ value, setValue ] = useState( '' );
-const Example = withState( {
- value: '',
-} )( ( { setState, value } ) => {
return (
setState( { value: newValue } ) }
+ onChange={ ( newValue ) => setValue( newValue ) }
value={ value }
/>
);
-} );
+};
export const Basic = () => ;