diff --git a/js/src/components/customer-effort-score-prompt/index.js b/js/src/components/customer-effort-score-prompt/index.js index d76845eece..88b1ef8ffc 100644 --- a/js/src/components/customer-effort-score-prompt/index.js +++ b/js/src/components/customer-effort-score-prompt/index.js @@ -10,6 +10,7 @@ import { recordEvent } from '@woocommerce/tracks'; */ import { LOCAL_STORAGE_KEYS } from '.~/constants'; import localStorage from '.~/utils/localStorage'; +import useEffectRemoveNotice from '.~/hooks/useEffectRemoveNotice'; /** * CES prompt snackbar open @@ -47,6 +48,11 @@ import localStorage from '.~/utils/localStorage'; * @return {JSX.Element} Rendered element. */ const CustomerEffortScorePrompt = ( { eventContext, label } ) => { + // NOTE: Currently CES Prompts uses core/notices2 as a store key, this seems something temporal + // and probably will be needed to change back to core/notices. + // See: https://github.com/woocommerce/woocommerce/blob/6.6.0/packages/js/notices/src/store/index.js + useEffectRemoveNotice( label, 'core/notices2' ); + const removeCESPromptFlagFromLocal = () => { localStorage.remove( LOCAL_STORAGE_KEYS.CAN_ONBOARDING_SETUP_CES_PROMPT_OPEN diff --git a/js/src/data/constants.js b/js/src/data/constants.js index b7e811df24..0cdf8699ce 100644 --- a/js/src/data/constants.js +++ b/js/src/data/constants.js @@ -1,2 +1,3 @@ export const STORE_KEY = 'wc/gla'; export const API_NAMESPACE = '/wc/gla'; +export const NOTICES_STORE_KEY = 'core/notices'; diff --git a/js/src/hooks/useEffectRemoveNotice.js b/js/src/hooks/useEffectRemoveNotice.js new file mode 100644 index 0000000000..c6803ddc65 --- /dev/null +++ b/js/src/hooks/useEffectRemoveNotice.js @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import { useEffect } from '@wordpress/element'; +import { dispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import useNotices from '.~/hooks/useNotices'; +import { NOTICES_STORE_KEY } from '.~/data/constants'; + +/** + * Search for a notice with specific label and remove it if the component is unmounted. + * + * @param {string} label the notice label + * @param {string} [storeKey] the store + * + * @return {import('@wordpress/notices').Notice|null} The notice to be removed otherwise null if it is not found + */ +const useEffectRemoveNotice = ( label, storeKey = NOTICES_STORE_KEY ) => { + const notices = useNotices( storeKey ); + const notice = notices.find( ( el ) => el.content === label ); + + useEffect( () => { + const { removeNotice } = dispatch( storeKey ); + + return () => { + if ( notice ) { + removeNotice( notice.id ); + } + }; + }, [ notice, storeKey ] ); + + return notice || null; +}; + +export default useEffectRemoveNotice; diff --git a/js/src/hooks/useEffectRemoveNotice.test.js b/js/src/hooks/useEffectRemoveNotice.test.js new file mode 100644 index 0000000000..4ed3729d48 --- /dev/null +++ b/js/src/hooks/useEffectRemoveNotice.test.js @@ -0,0 +1,76 @@ +/** + * External dependencies + */ +import { renderHook } from '@testing-library/react-hooks'; + +/** + * Internal dependencies + */ +import useEffectRemoveNotice from './useEffectRemoveNotice'; +import useNotices from '.~/hooks/useNotices'; +import { NOTICES_STORE_KEY } from '.~/data/constants'; + +const mockRemoveNotice = jest.fn(); + +jest.mock( '.~/hooks/useNotices', () => { + return jest.fn(); +} ); + +jest.mock( '@wordpress/data', () => { + return { + dispatch: jest.fn().mockImplementation( () => ( { + removeNotice: mockRemoveNotice, + } ) ), + }; +} ); + +describe( 'useEffectRemoveNotice', () => { + const testLabel = 'test_label'; + const notice = { + id: 1, + content: testLabel, + }; + + beforeEach( () => { + jest.clearAllMocks(); + useNotices.mockImplementation( () => [ notice ] ); + } ); + + test( 'Should return with the notice', () => { + const { result } = renderHook( () => + useEffectRemoveNotice( testLabel ) + ); + + expect( mockRemoveNotice ).toHaveBeenCalledTimes( 0 ); + expect( useNotices ).toHaveBeenCalledWith( NOTICES_STORE_KEY ); + + expect( result.current ).toEqual( notice ); + } ); + + test( 'Should return with null if the notice is not found', () => { + useNotices.mockImplementation( () => [ + { ...notice, content: 'different_labels' }, + ] ); + + const { result } = renderHook( () => + useEffectRemoveNotice( testLabel ) + ); + + expect( mockRemoveNotice ).toHaveBeenCalledTimes( 0 ); + expect( useNotices ).toHaveBeenCalledWith( NOTICES_STORE_KEY ); + + expect( result.current ).toEqual( null ); + } ); + + test( 'Should remove notice when the hook is unmounted', () => { + const { result, unmount } = renderHook( () => + useEffectRemoveNotice( testLabel ) + ); + + unmount(); + + expect( mockRemoveNotice ).toHaveBeenCalledTimes( 1 ); + expect( mockRemoveNotice ).toHaveBeenCalledWith( notice.id ); + expect( result.current ).toEqual( notice ); + } ); +} ); diff --git a/js/src/hooks/useNotices.js b/js/src/hooks/useNotices.js new file mode 100644 index 0000000000..4c5ecc7ece --- /dev/null +++ b/js/src/hooks/useNotices.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { NOTICES_STORE_KEY } from '.~/data/constants'; + +/** + * @typedef {import('@wordpress/notices').Notice} Notice + */ + +/** + * A hook that returns the WP notices + * + * @param {string} [storeKey] The store key + * + * @return {Array} Returns the Notices + */ +const useNotices = ( storeKey = NOTICES_STORE_KEY ) => { + return useSelect( + ( select ) => { + const selector = select( storeKey ); + return selector.getNotices(); + }, + [ storeKey ] + ); +}; + +export default useNotices;