Skip to content
47 changes: 47 additions & 0 deletions docs/reference-guides/data/data-core-notices.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,53 @@ _Returns_

- `Object`: Action object.

### removeAllNotices

Removes all notices from a given context. Defaults to the default context.

_Usage_

```js
import { __ } from '@wordpress/i18n';
import { useDispatch, useSelect } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
import { Button } from '@wordpress/components';

export const ExampleComponent = () => {
const notices = useSelect( ( select ) =>
select( noticesStore ).getNotices()
);
const { removeNotices } = useDispatch( noticesStore );
return (
<>
<ul>
{ notices.map( ( notice ) => (
<li key={ notice.id }>{ notice.content }</li>
) ) }
</ul>
<Button onClick={ () => removeAllNotices() }>
{ __( 'Clear all notices', 'woo-gutenberg-products-block' ) }
</Button>
<Button onClick={ () => removeAllNotices( 'snackbar' ) }>
{ __(
'Clear all snackbar notices',
'woo-gutenberg-products-block'
) }
</Button>
</>
);
};
```

_Parameters_

- _noticeType_ `string`: The context to remove all notices from.
- _context_ `string`: The context to remove all notices from.

_Returns_

- `Object`: Action object.

### removeNotice

Returns an action object used in signalling that a notice is to be removed.
Expand Down
1 change: 1 addition & 0 deletions packages/notices/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### New Feature

- Add a new action `removeNotices` which allows bulk removal of notices by their IDs. ([#39940](https://github.com/WordPress/gutenberg/pull/39940))
- Add a new action `removeAllNotices` which removes all notices from a given context. ([#44059](https://github.com/WordPress/gutenberg/pull/44059))

## 4.2.0 (2023-05-24)

Expand Down
57 changes: 57 additions & 0 deletions packages/notices/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,63 @@ export function removeNotice( id, context = DEFAULT_CONTEXT ) {
};
}

/**
* Removes all notices from a given context. Defaults to the default context.
*
* @param {string} noticeType The context to remove all notices from.
* @param {string} context The context to remove all notices from.
*
* @example
* ```js
* import { __ } from '@wordpress/i18n';
* import { useDispatch, useSelect } from '@wordpress/data';
* import { store as noticesStore } from '@wordpress/notices';
* import { Button } from '@wordpress/components';
*
* export const ExampleComponent = () => {
* const notices = useSelect( ( select ) =>
* select( noticesStore ).getNotices()
* );
* const { removeNotices } = useDispatch( noticesStore );
* return (
* <>
* <ul>
* { notices.map( ( notice ) => (
* <li key={ notice.id }>{ notice.content }</li>
* ) ) }
* </ul>
* <Button
* onClick={ () =>
* removeAllNotices()
* }
* >
* { __( 'Clear all notices', 'woo-gutenberg-products-block' ) }
* </Button>
* <Button
* onClick={ () =>
* removeAllNotices( 'snackbar' )
* }
* >
* { __( 'Clear all snackbar notices', 'woo-gutenberg-products-block' ) }
* </Button>
* </>
* );
* };
* ```
*
* @return {Object} Action object.
*/
export function removeAllNotices(
noticeType = 'default',
context = DEFAULT_CONTEXT
) {
return {
type: 'REMOVE_ALL_NOTICES',
noticeType,
context,
};
}

/**
* Returns an action object used in signalling that several notices are to be removed.
*
Expand Down
3 changes: 3 additions & 0 deletions packages/notices/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const notices = onSubKey( 'context' )( ( state = [], action ) => {

case 'REMOVE_NOTICES':
return state.filter( ( { id } ) => ! action.ids.includes( id ) );

case 'REMOVE_ALL_NOTICES':
return state.filter( ( { type } ) => type !== action.noticeType );
Comment on lines +30 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't considering the provided context in the action object. Should it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't considering the provided context in the action object. Should it?

The onSubKey in the reducer takes care of that actually.

I have tests that cover removing notices from a specific context: https://github.com/wordpress/gutenberg/blob/2a5db119b8ed8e16002de157a5edf37ff47dffe6/packages/notices/src/store/test/reducer.js#L230

}

return state;
Expand Down
29 changes: 29 additions & 0 deletions packages/notices/src/store/test/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
createErrorNotice,
createWarningNotice,
removeNotice,
removeAllNotices,
removeNotices,
} from '../actions';
import { DEFAULT_CONTEXT, DEFAULT_STATUS } from '../constants';
Expand Down Expand Up @@ -239,4 +240,32 @@ describe( 'actions', () => {
} );
} );
} );

describe( 'removeAllNotices', () => {
it( 'should return action', () => {
expect( removeAllNotices() ).toEqual( {
type: 'REMOVE_ALL_NOTICES',
noticeType: 'default',
context: DEFAULT_CONTEXT,
} );
} );

it( 'should return action with custom context', () => {
const context = 'foo';

expect( removeAllNotices( 'default', context ) ).toEqual( {
type: 'REMOVE_ALL_NOTICES',
noticeType: 'default',
context,
} );
} );

it( 'should return action with type', () => {
expect( removeAllNotices( 'snackbar' ) ).toEqual( {
type: 'REMOVE_ALL_NOTICES',
noticeType: 'snackbar',
context: DEFAULT_CONTEXT,
} );
} );
} );
} );
87 changes: 86 additions & 1 deletion packages/notices/src/store/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import deepFreeze from 'deep-freeze';
* Internal dependencies
*/
import reducer from '../reducer';
import { createNotice, removeNotice, removeNotices } from '../actions';
import {
createNotice,
removeNotice,
removeNotices,
removeAllNotices,
} from '../actions';
import { getNotices } from '../selectors';
import { DEFAULT_CONTEXT } from '../constants';

Expand Down Expand Up @@ -208,4 +213,84 @@ describe( 'reducer', () => {
],
} );
} );

it( 'should remove all notices', () => {
let action = createNotice( 'error', 'save error' );
const original = deepFreeze( reducer( undefined, action ) );

action = createNotice( 'success', 'successfully saved' );
let state = reducer( original, action );
state = reducer( state, removeAllNotices() );

expect( state ).toEqual( {
[ DEFAULT_CONTEXT ]: [],
} );
} );

it( 'should remove all notices in a given context but leave other contexts intact', () => {
let action = createNotice( 'error', 'save error', {
context: 'foo',
id: 'foo-error',
} );
const original = deepFreeze( reducer( undefined, action ) );

action = createNotice( 'success', 'successfully saved', {
context: 'bar',
} );

let state = reducer( original, action );
state = reducer( state, removeAllNotices( 'default', 'bar' ) );

expect( state ).toEqual( {
bar: [],
foo: [
{
id: 'foo-error',
content: 'save error',
spokenMessage: 'save error',
__unstableHTML: undefined,
status: 'error',
isDismissible: true,
actions: [],
type: 'default',
icon: null,
explicitDismiss: false,
onDismiss: undefined,
},
],
} );
} );

it( 'should remove all notices of a given type', () => {
let action = createNotice( 'error', 'save error', {
id: 'global-error',
} );
const original = deepFreeze( reducer( undefined, action ) );

action = createNotice( 'success', 'successfully saved', {
type: 'snackbar',
id: 'snackbar-success',
} );

let state = reducer( original, action );
state = reducer( state, removeAllNotices( 'default' ) );

expect( state ).toEqual( {
[ DEFAULT_CONTEXT ]: [
{
id: 'snackbar-success',
content: 'successfully saved',
spokenMessage: 'successfully saved',
__unstableHTML: undefined,
status: 'success',
isDismissible: true,
actions: [],
type: 'snackbar',
icon: null,
explicitDismiss: false,
onDismiss: undefined,
},
],
} );
} );
} );