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 ( + <> + + + + ); +}; +``` + +_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 ( + * <> + * + * + * + * ); + * }; + * ``` + * @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',