Skip to content

Commit

Permalink
[@wordpress/notices] Add a new action removeNotices which allows …
Browse files Browse the repository at this point in the history
…bulk removal of notices (WordPress#39940)

* Add removeNotices action creator

* Handle REMOVE_NOTICES in reducer

* Add tests for reducer and actions

* Move test to better position and add one for multiple contexts

* Use .filter instead of reject

* Add example component to doc block

* Add changelog entry

* Correct spacing on list item

* Add correct changelog heading
  • Loading branch information
opr authored and sethrubenstein committed Jul 13, 2023
1 parent e914cdf commit fad4646
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 1 deletion.
45 changes: 45 additions & 0 deletions docs/reference-guides/data/data-core-notices.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
<ul>
{ notices.map( ( notice ) => (
<li key={ notice.id }>{ notice.content }</li>
) ) }
</ul>
<Button
onClick={ () =>
removeNotices( notices.map( ( { id } ) => id ) )
}
>
{ __( 'Clear all notices' ) }
</Button>
</>
);
};
```

_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.

<!-- END TOKEN(Autogenerated actions|../../../packages/notices/src/store/actions.js) -->
4 changes: 4 additions & 0 deletions packages/notices/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
46 changes: 46 additions & 0 deletions packages/notices/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
* <>
* <ul>
* { notices.map( ( notice ) => (
* <li key={ notice.id }>{ notice.content }</li>
* ) ) }
* </ul>
* <Button
* onClick={ () =>
* removeNotices( notices.map( ( { id } ) => id ) )
* }
* >
* { __( 'Clear all notices' ) }
* </Button>
* </>
* );
* };
* ```
* @return {Object} Action object.
*/
export function removeNotices( ids, context = DEFAULT_CONTEXT ) {
return {
type: 'REMOVE_NOTICES',
ids,
context,
};
}
3 changes: 3 additions & 0 deletions packages/notices/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 24 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,
removeNotices,
} from '../actions';
import { DEFAULT_CONTEXT, DEFAULT_STATUS } from '../constants';

Expand Down Expand Up @@ -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,
} );
} );
} );
} );
40 changes: 39 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,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';

Expand Down Expand Up @@ -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',
Expand Down

0 comments on commit fad4646

Please sign in to comment.