- { __(
- 'Use your left or right arrow keys or drag and drop with the mouse to change the gradient position. Press the button to change the color or remove the control point.'
- ) }
-
+ { __(
+ 'Use your left or right arrow keys or drag and drop with the mouse to change the gradient position. Press the button to change the color or remove the control point.'
+ ) }
+
+
+ );
+}
export default function ControlPoints( {
gradientPickerDomRef,
diff --git a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap
index c06662ee862c99..dddc2093bca480 100644
--- a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap
+++ b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DimensionControl rendering renders with custom sizes 1`] = `
-
);
}
-
-export default withInstanceId( FontSizePicker );
diff --git a/packages/components/src/form-token-field/token.js b/packages/components/src/form-token-field/token.js
index 0e83faa1733093..565e3af635e958 100644
--- a/packages/components/src/form-token-field/token.js
+++ b/packages/components/src/form-token-field/token.js
@@ -7,7 +7,7 @@ import { noop } from 'lodash';
/**
* WordPress dependencies
*/
-import { withInstanceId } from '@wordpress/compose';
+import { useInstanceId } from '@wordpress/compose';
import { __, sprintf } from '@wordpress/i18n';
/**
@@ -16,7 +16,7 @@ import { __, sprintf } from '@wordpress/i18n';
import IconButton from '../icon-button';
import VisuallyHidden from '../visually-hidden';
-function Token( {
+export default function Token( {
value,
status,
title,
@@ -29,8 +29,8 @@ function Token( {
messages,
termPosition,
termsCount,
- instanceId,
} ) {
+ const instanceId = useInstanceId();
const tokenClasses = classnames( 'components-form-token-field__token', {
'is-error': 'error' === status,
'is-success': 'success' === status,
@@ -75,5 +75,3 @@ function Token( {
);
}
-
-export default withInstanceId( Token );
diff --git a/packages/components/src/menu-group/index.js b/packages/components/src/menu-group/index.js
index ba98c5fea4fd22..a10235c1f7f173 100644
--- a/packages/components/src/menu-group/index.js
+++ b/packages/components/src/menu-group/index.js
@@ -7,14 +7,15 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { Children } from '@wordpress/element';
-import { withInstanceId } from '@wordpress/compose';
+import { useInstanceId } from '@wordpress/compose';
export function MenuGroup( {
children,
className = '',
- instanceId,
label,
} ) {
+ const instanceId = useInstanceId();
+
if ( ! Children.count( children ) ) {
return null;
}
@@ -43,4 +44,4 @@ export function MenuGroup( {
);
}
-export default withInstanceId( MenuGroup );
+export default MenuGroup;
diff --git a/packages/components/src/radio-control/index.js b/packages/components/src/radio-control/index.js
index 2aec3eaca43d48..67b61e6bde8a37 100644
--- a/packages/components/src/radio-control/index.js
+++ b/packages/components/src/radio-control/index.js
@@ -7,14 +7,15 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
-import { withInstanceId } from '@wordpress/compose';
+import { useInstanceId } from '@wordpress/compose';
/**
* Internal dependencies
*/
import BaseControl from '../base-control';
-function RadioControl( { label, className, selected, help, instanceId, onChange, options = [] } ) {
+export default function RadioControl( { label, className, selected, help, onChange, options = [] } ) {
+ const instanceId = useInstanceId();
const id = `inspector-radio-control-${ instanceId }`;
const onChangeValue = ( event ) => onChange( event.target.value );
@@ -43,5 +44,3 @@ function RadioControl( { label, className, selected, help, instanceId, onChange,
);
}
-
-export default withInstanceId( RadioControl );
diff --git a/packages/components/src/select-control/index.js b/packages/components/src/select-control/index.js
index a10f051face1c0..9ae37ecbb5429f 100644
--- a/packages/components/src/select-control/index.js
+++ b/packages/components/src/select-control/index.js
@@ -6,16 +6,15 @@ import { isEmpty } from 'lodash';
/**
* WordPress dependencies
*/
-import { withInstanceId } from '@wordpress/compose';
+import { useInstanceId } from '@wordpress/compose';
/**
* Internal dependencies
*/
import BaseControl from '../base-control';
-function SelectControl( {
+export default function SelectControl( {
help,
- instanceId,
label,
multiple = false,
onChange,
@@ -24,6 +23,7 @@ function SelectControl( {
hideLabelFromVision,
...props
} ) {
+ const instanceId = useInstanceId();
const id = `inspector-select-control-${ instanceId }`;
const onChangeValue = ( event ) => {
if ( multiple ) {
@@ -62,5 +62,3 @@ function SelectControl( {
);
/* eslint-enable jsx-a11y/no-onchange */
}
-
-export default withInstanceId( SelectControl );
diff --git a/packages/components/src/text-control/index.js b/packages/components/src/text-control/index.js
index 61504cc7da5cfb..e79aa90bbac807 100644
--- a/packages/components/src/text-control/index.js
+++ b/packages/components/src/text-control/index.js
@@ -1,14 +1,15 @@
/**
* WordPress dependencies
*/
-import { withInstanceId } from '@wordpress/compose';
+import { useInstanceId } from '@wordpress/compose';
/**
* Internal dependencies
*/
import BaseControl from '../base-control';
-function TextControl( { label, hideLabelFromVision, value, help, className, instanceId, onChange, type = 'text', ...props } ) {
+export default function TextControl( { label, hideLabelFromVision, value, help, className, onChange, type = 'text', ...props } ) {
+ const instanceId = useInstanceId();
const id = `inspector-text-control-${ instanceId }`;
const onChangeValue = ( event ) => onChange( event.target.value );
@@ -25,5 +26,3 @@ function TextControl( { label, hideLabelFromVision, value, help, className, inst
);
}
-
-export default withInstanceId( TextControl );
diff --git a/packages/components/src/textarea-control/index.js b/packages/components/src/textarea-control/index.js
index 45158ca2ec7474..522b38a36086e6 100644
--- a/packages/components/src/textarea-control/index.js
+++ b/packages/components/src/textarea-control/index.js
@@ -1,14 +1,15 @@
/**
* WordPress dependencies
*/
-import { withInstanceId } from '@wordpress/compose';
+import { useInstanceId } from '@wordpress/compose';
/**
* Internal dependencies
*/
import BaseControl from '../base-control';
-function TextareaControl( { label, hideLabelFromVision, value, help, instanceId, onChange, rows = 4, className, ...props } ) {
+export default function TextareaControl( { label, hideLabelFromVision, value, help, onChange, rows = 4, className, ...props } ) {
+ const instanceId = useInstanceId();
const id = `inspector-textarea-control-${ instanceId }`;
const onChangeValue = ( event ) => onChange( event.target.value );
@@ -26,5 +27,3 @@ function TextareaControl( { label, hideLabelFromVision, value, help, instanceId,
);
}
-
-export default withInstanceId( TextareaControl );
diff --git a/packages/compose/README.md b/packages/compose/README.md
index dcd5c47411d74c..97bd40880bd421 100644
--- a/packages/compose/README.md
+++ b/packages/compose/README.md
@@ -119,6 +119,10 @@ _Returns_
- `WPComponent`: Component class with generated display name assigned.
+# **useInstanceId**
+
+Provides a unique instance ID.
+
# **useMediaQuery**
Runs a media query and returns its value when it changes.
diff --git a/packages/compose/src/hooks/use-instance-id/README.md b/packages/compose/src/hooks/use-instance-id/README.md
new file mode 100644
index 00000000000000..352ea0917717fd
--- /dev/null
+++ b/packages/compose/src/hooks/use-instance-id/README.md
@@ -0,0 +1,22 @@
+useInstanceId
+==============
+
+Some components need to generate a unique id for each instance. This could serve as suffixes to element ID's for example. `useInstanceId` provides a unique `instanceId` to serve this purpose.
+
+## Usage
+
+```jsx
+/**
+ * WordPress dependencies
+ */
+import { useInstanceId } from '@wordpress/compose';
+
+function MyCustomElement() {
+ const instanceId = useInstanceId();
+ return (
+
+ content
+
+ );
+}
+```
diff --git a/packages/compose/src/hooks/use-instance-id/index.js b/packages/compose/src/hooks/use-instance-id/index.js
new file mode 100644
index 00000000000000..4b67c5b608a4e1
--- /dev/null
+++ b/packages/compose/src/hooks/use-instance-id/index.js
@@ -0,0 +1,48 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo, useEffect } from '@wordpress/element';
+
+/**
+ * Next id to use, if there are no free ids.
+ */
+let nextId = 0;
+
+/**
+ * Array to keep track of free ids.
+ */
+const freedIds = [];
+
+/**
+ * Find a free id.
+ */
+function findId() {
+ if ( freedIds.length ) {
+ return freedIds.pop();
+ }
+
+ return nextId++;
+}
+
+/**
+ * Free an id.
+ *
+ * @param {number} id Id to free.
+ */
+function freeId( id ) {
+ freedIds.push( id );
+}
+
+/**
+ * Provides a unique instance ID.
+ */
+export default function useInstanceId() {
+ // Take advantage of useMemo to get the same id throughout the life of a
+ // component.
+ const id = useMemo( findId, [] );
+ // Free up the id when the comonent unmounts. This must depend on `id` since
+ // useMemo is not guaranteed to return the same id throughout the life of
+ // the component.
+ useEffect( () => () => freeId( id ), [ id ] );
+ return id;
+}
diff --git a/packages/compose/src/hooks/use-instance-id/test/index.js b/packages/compose/src/hooks/use-instance-id/test/index.js
new file mode 100644
index 00000000000000..76daa8bba6460e
--- /dev/null
+++ b/packages/compose/src/hooks/use-instance-id/test/index.js
@@ -0,0 +1,54 @@
+/**
+ * External dependencies
+ */
+import { create, act } from 'react-test-renderer';
+
+/**
+ * Internal dependencies
+ */
+import useInstanceId from '../';
+
+describe( 'useInstanceId', () => {
+ const TestComponent = () => {
+ return useInstanceId();
+ };
+
+ it( 'should manage ids', async () => {
+ let test0;
+
+ await act( async () => {
+ test0 = create( );
+ } );
+
+ expect( test0.toJSON() ).toBe( '0' );
+
+ let test1;
+
+ await act( async () => {
+ test1 = create( );
+ } );
+
+ expect( test1.toJSON() ).toBe( '1' );
+
+ test0.unmount();
+
+ let test2;
+
+ await act( async () => {
+ test2 = create( );
+ } );
+
+ expect( test2.toJSON() ).toBe( '0' );
+
+ let test3;
+
+ await act( async () => {
+ test3 = create( );
+ } );
+
+ expect( test3.toJSON() ).toBe( '2' );
+
+ test1.unmount();
+ test2.unmount();
+ } );
+} );
diff --git a/packages/compose/src/index.js b/packages/compose/src/index.js
index aa1d03dcde2f7d..7be88558f8b887 100644
--- a/packages/compose/src/index.js
+++ b/packages/compose/src/index.js
@@ -16,3 +16,4 @@ export { default as withState } from './higher-order/with-state';
export { default as useMediaQuery } from './hooks/use-media-query';
export { default as useReducedMotion } from './hooks/use-reduced-motion';
export { default as useViewportMatch } from './hooks/use-viewport-match';
+export { default as useInstanceId } from './hooks/use-instance-id';
diff --git a/packages/e2e-tests/specs/editor/various/embedding.test.js b/packages/e2e-tests/specs/editor/various/embedding.test.js
index 53dec261440586..fe5a6ac3c7ca58 100644
--- a/packages/e2e-tests/specs/editor/various/embedding.test.js
+++ b/packages/e2e-tests/specs/editor/various/embedding.test.js
@@ -251,7 +251,7 @@ describe( 'Embedding content', () => {
await insertBlock( 'Paragraph' );
await page.keyboard.type( 'Hello there!' );
await publishPost();
- const postUrl = await page.$eval( '#inspector-text-control-0', ( el ) => el.value );
+ const postUrl = await page.$eval( '.post-publish-panel__postpublish-post-address .components-text-control__input', ( el ) => el.value );
// Start a new post, embed the previous post.
await createNewPost();
diff --git a/packages/edit-post/src/components/manage-blocks-modal/show-all.js b/packages/edit-post/src/components/manage-blocks-modal/show-all.js
index 7aaa206bc626ac..5fa7c665d69a03 100644
--- a/packages/edit-post/src/components/manage-blocks-modal/show-all.js
+++ b/packages/edit-post/src/components/manage-blocks-modal/show-all.js
@@ -1,11 +1,12 @@
/**
* WordPress dependencies
*/
-import { withInstanceId } from '@wordpress/compose';
+import { useInstanceId } from '@wordpress/compose';
import { FormToggle } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
-function BlockManagerShowAll( { instanceId, checked, onChange } ) {
+export default function BlockManagerShowAll( { checked, onChange } ) {
+ const instanceId = useInstanceId();
const id = 'edit-post-manage-blocks-modal__show-all-' + instanceId;
return (
@@ -27,5 +28,3 @@ function BlockManagerShowAll( { instanceId, checked, onChange } ) {
);
}
-
-export default withInstanceId( BlockManagerShowAll );
diff --git a/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap
index 7cb1c2b47f7b23..e936a3b03a821a 100644
--- a/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap
+++ b/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap
@@ -28,7 +28,7 @@ exports[`PostPublishPanel should render the post-publish panel if the post is pu
-
@@ -63,7 +63,7 @@ exports[`PostPublishPanel should render the post-publish panel if the post is sc
-
@@ -99,7 +99,7 @@ exports[`PostPublishPanel should render the pre-publish panel if post status is
-
@@ -135,7 +135,7 @@ exports[`PostPublishPanel should render the pre-publish panel if the post is not
-
@@ -171,7 +171,7 @@ exports[`PostPublishPanel should render the spinner if the post is being saved 1