diff --git a/docs/reference-guides/data/data-core-notices.md b/docs/reference-guides/data/data-core-notices.md
index c5e3bc017a9146..18fcf37ad224b9 100644
--- a/docs/reference-guides/data/data-core-notices.md
+++ b/docs/reference-guides/data/data-core-notices.md
@@ -309,4 +309,49 @@ _Returns_
- `Object`: Action object.
+### removeNotices
+
+Returns an action object used in signalling that several notices are to be removed.
+
+_Usage_
+
+```js
+import { __ } from '@wordpress/i18n';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { store as noticesStore } from '@wordpress/notices';
+import { Button } from '@wordpress/components';
+
+const ExampleComponent = () => {
+ const notices = useSelect( ( select ) =>
+ select( noticesStore ).getNotices()
+ );
+ const { removeNotices } = useDispatch( noticesStore );
+ return (
+ <>
+
+ { notices.map( ( notice ) => (
+ - { notice.content }
+ ) ) }
+
+
+ >
+ );
+};
+```
+
+_Parameters_
+
+- _ids_ `string[]`: List of unique notice identifiers.
+- _context_ `[string]`: Optional context (grouping) in which the notices are intended to appear. Defaults to default context.
+
+_Returns_
+
+- `Object`: Action object.
+
diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md
index 15e471a65d36a5..770b0b532e662c 100644
--- a/packages/notices/CHANGELOG.md
+++ b/packages/notices/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### New Feature
+
+- Add a new action `removeNotices` which allows bulk removal of notices by their IDs. ([#39940](https://github.com/WordPress/gutenberg/pull/39940))
+
## 4.2.0 (2023-05-24)
## 4.1.0 (2023-05-10)
diff --git a/packages/notices/src/store/actions.js b/packages/notices/src/store/actions.js
index a8fdb13f9352fc..0ddcc769c51467 100644
--- a/packages/notices/src/store/actions.js
+++ b/packages/notices/src/store/actions.js
@@ -313,3 +313,49 @@ export function removeNotice( id, context = DEFAULT_CONTEXT ) {
context,
};
}
+
+/**
+ * Returns an action object used in signalling that several notices are to be removed.
+ *
+ * @param {string[]} ids List of unique notice identifiers.
+ * @param {string} [context='global'] Optional context (grouping) in which the notices are
+ * intended to appear. Defaults to default context.
+ * @example
+ * ```js
+ * import { __ } from '@wordpress/i18n';
+ * import { useDispatch, useSelect } from '@wordpress/data';
+ * import { store as noticesStore } from '@wordpress/notices';
+ * import { Button } from '@wordpress/components';
+ *
+ * const ExampleComponent = () => {
+ * const notices = useSelect( ( select ) =>
+ * select( noticesStore ).getNotices()
+ * );
+ * const { removeNotices } = useDispatch( noticesStore );
+ * return (
+ * <>
+ *
+ * { notices.map( ( notice ) => (
+ * - { notice.content }
+ * ) ) }
+ *
+ *
+ * >
+ * );
+ * };
+ * ```
+ * @return {Object} Action object.
+ */
+export function removeNotices( ids, context = DEFAULT_CONTEXT ) {
+ return {
+ type: 'REMOVE_NOTICES',
+ ids,
+ context,
+ };
+}
diff --git a/packages/notices/src/store/reducer.js b/packages/notices/src/store/reducer.js
index ff2359b61cc013..6fed169af59869 100644
--- a/packages/notices/src/store/reducer.js
+++ b/packages/notices/src/store/reducer.js
@@ -23,6 +23,9 @@ const notices = onSubKey( 'context' )( ( state = [], action ) => {
case 'REMOVE_NOTICE':
return state.filter( ( { id } ) => id !== action.id );
+
+ case 'REMOVE_NOTICES':
+ return state.filter( ( { id } ) => ! action.ids.includes( id ) );
}
return state;
diff --git a/packages/notices/src/store/test/actions.js b/packages/notices/src/store/test/actions.js
index db6aa72b468dec..ed355c045e1b54 100644
--- a/packages/notices/src/store/test/actions.js
+++ b/packages/notices/src/store/test/actions.js
@@ -8,6 +8,7 @@ import {
createErrorNotice,
createWarningNotice,
removeNotice,
+ removeNotices,
} from '../actions';
import { DEFAULT_CONTEXT, DEFAULT_STATUS } from '../constants';
@@ -215,4 +216,27 @@ describe( 'actions', () => {
} );
} );
} );
+
+ describe( 'removeNotices', () => {
+ it( 'should return action', () => {
+ const ids = [ 'id', 'id2' ];
+
+ expect( removeNotices( ids ) ).toEqual( {
+ type: 'REMOVE_NOTICES',
+ ids,
+ context: DEFAULT_CONTEXT,
+ } );
+ } );
+
+ it( 'should return action with custom context', () => {
+ const ids = [ 'id', 'id2' ];
+ const context = 'foo';
+
+ expect( removeNotices( ids, context ) ).toEqual( {
+ type: 'REMOVE_NOTICES',
+ ids,
+ context,
+ } );
+ } );
+ } );
} );
diff --git a/packages/notices/src/store/test/reducer.js b/packages/notices/src/store/test/reducer.js
index 52ba278c79d854..fe0e33b478bd63 100644
--- a/packages/notices/src/store/test/reducer.js
+++ b/packages/notices/src/store/test/reducer.js
@@ -7,7 +7,7 @@ import deepFreeze from 'deep-freeze';
* Internal dependencies
*/
import reducer from '../reducer';
-import { createNotice, removeNotice } from '../actions';
+import { createNotice, removeNotice, removeNotices } from '../actions';
import { getNotices } from '../selectors';
import { DEFAULT_CONTEXT } from '../constants';
@@ -141,6 +141,44 @@ describe( 'reducer', () => {
expect( state[ DEFAULT_CONTEXT ] ).toHaveLength( 1 );
} );
+ it( 'should omit several removed notices', () => {
+ const action = createNotice( 'error', 'save error' );
+ const action2 = createNotice( 'error', 'second error' );
+ const stateWithOneNotice = reducer( undefined, action );
+ const original = deepFreeze( reducer( stateWithOneNotice, action2 ) );
+ const ids = [
+ getNotices( original )[ 0 ].id,
+ getNotices( original )[ 1 ].id,
+ ];
+
+ const state = reducer( original, removeNotices( ids ) );
+
+ expect( state ).toEqual( {
+ [ DEFAULT_CONTEXT ]: [],
+ } );
+ } );
+
+ it( 'should omit several removed notices across contexts', () => {
+ const action = createNotice( 'error', 'save error' );
+ const action2 = createNotice( 'error', 'second error', {
+ context: 'foo',
+ } );
+ const action3 = createNotice( 'error', 'third error', {
+ context: 'foo',
+ } );
+ const stateWithOneNotice = reducer( undefined, action );
+ const stateWithTwoNotices = reducer( stateWithOneNotice, action2 );
+ const original = deepFreeze( reducer( stateWithTwoNotices, action3 ) );
+ const ids = [
+ getNotices( original, 'foo' )[ 0 ].id,
+ getNotices( original, 'foo' )[ 1 ].id,
+ ];
+
+ const state = reducer( original, removeNotices( ids, 'foo' ) );
+
+ expect( state[ DEFAULT_CONTEXT ] ).toHaveLength( 1 );
+ } );
+
it( 'should dedupe distinct ids, preferring new', () => {
let action = createNotice( 'error', 'save error (1)', {
id: 'error-message',