diff --git a/package.json b/package.json index 31c133e..b839f41 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ "lint": "aqu lint", "lint:fix": "aqu lint --fix", "test": "aqu test", - "test:watch": "npm test -- --watch", - "test:coverage": "npm test -- --coverage --json --silent --ci --outputFile=\"report.json\"", - "test:log-coverage": "npm test -- --coverage --silent --ci --coverageReporters=text", + "test:watch": "pnpm test -- --watch", + "test:coverage": "pnpm test -- --coverage --json --silent --ci --outputFile=\"report.json\"", + "test:log-coverage": "pnpm test -- --coverage --silent --ci --coverageReporters=text", "release": "np", "prepublishOnly": "aqu build" }, diff --git a/src/hooks/usePopup.ts b/src/hooks/usePopup.ts index 857be22..46fe0ed 100644 --- a/src/hooks/usePopup.ts +++ b/src/hooks/usePopup.ts @@ -1,4 +1,4 @@ -import { ComponentType, useCallback, useRef } from 'react'; +import { ComponentType, useCallback, useEffect, useRef } from 'react'; import { nanoid } from 'nanoid/non-secure'; import { useEvent } from './useEvent'; @@ -18,7 +18,7 @@ export const usePopup = ( props: Pick, group: PopupGroup ): UsePopupBag => { - const { mount, close: closePopup, unmount } = usePopupsContext(); + const { mount, close: closePopup, unmount, update } = usePopupsContext(); const popupIdentifier = useRef({ id: nanoid(), @@ -46,5 +46,9 @@ export const usePopup = ( closePopup(popupIdentifier.current); }, [closePopup]); + useEffect(() => { + update(popupIdentifier.current, props); + }, [props, update]); + return [open, close]; }; diff --git a/src/hooks/usePopupsBag.ts b/src/hooks/usePopupsBag.ts index 54508d2..6147f4a 100644 --- a/src/hooks/usePopupsBag.ts +++ b/src/hooks/usePopupsBag.ts @@ -36,6 +36,19 @@ export const usePopupsBag = (): PopupsBag => { return popup.popupIdentifier; }, []); + const update = useCallback( + (popupIdentifier: PopupIdentifier, props: object) => { + dispatch({ + type: ActionType.UPDATE, + payload: { + popupIdentifier, + props, + }, + }); + }, + [] + ); + const close = useCallback( (popupIdentifier: PopupIdentifier) => { const popup = getPopup(popupIdentifier); @@ -47,6 +60,7 @@ export const usePopupsBag = (): PopupsBag => { return { mount, unmount, + update, getPopup, close, popupsState, diff --git a/src/types/PopupsBag.ts b/src/types/PopupsBag.ts index 50e9341..d822a5b 100644 --- a/src/types/PopupsBag.ts +++ b/src/types/PopupsBag.ts @@ -5,6 +5,7 @@ import { PopupsState } from '../utils/popupsReducer'; export type PopupsBag = { mount:

(popup: Popup

) => PopupIdentifier; unmount: (popupIdentifier: PopupIdentifier) => void; + update: (popupIdentifier: PopupIdentifier, props: object) => void; close: (popupIdentifier: PopupIdentifier) => void; popupsState: PopupsState; diff --git a/src/utils/popupsReducer.ts b/src/utils/popupsReducer.ts index 4f1d285..1458c37 100644 --- a/src/utils/popupsReducer.ts +++ b/src/utils/popupsReducer.ts @@ -5,6 +5,7 @@ import { PopupsRegistry } from '../types/PopupsRegistry'; export enum ActionType { MOUNT, UNMOUNT, + UPDATE, } type MountAction = { @@ -21,14 +22,21 @@ type UnmountAction = { }; }; -export type PopupsAction = MountAction | UnmountAction; +type UpdateAction = { + type: ActionType.UPDATE; + payload: { + popupIdentifier: PopupIdentifier; + props: object; + }; +}; + +export type PopupsAction = MountAction | UnmountAction | UpdateAction; export type PopupsState = { popups: PopupsRegistry }; -export const popupsReducer = ( - { popups }: PopupsState, - action: PopupsAction -) => { +export const popupsReducer = (prevState: PopupsState, action: PopupsAction) => { + const { popups } = prevState; + switch (action.type) { case ActionType.MOUNT: { const { popup } = action.payload; @@ -47,6 +55,22 @@ export const popupsReducer = ( }; } + case ActionType.UPDATE: { + const { popupIdentifier, props } = action.payload; + + const { groupId, id } = popupIdentifier; + + if (!popups[groupId]?.[id]) { + return prevState; + } + + popups[groupId][id].props = props; + + return { + popups, + }; + } + case ActionType.UNMOUNT: { const { groupId, id } = action.payload.popupIdentifier; diff --git a/test/hooks/usePopup.test.tsx b/test/hooks/usePopup.test.tsx index 74c1cd2..09d356d 100644 --- a/test/hooks/usePopup.test.tsx +++ b/test/hooks/usePopup.test.tsx @@ -24,8 +24,9 @@ const PopupComponentWithProps: React.FC<{ describe('usePopup', () => { it('should render only one popup', () => { + const initialProps = {}; const { result } = renderHook( - () => usePopup(SimplePopupComponent, {}, group), + () => usePopup(SimplePopupComponent, initialProps, group), { wrapper: TestHookWrapper, } @@ -45,8 +46,9 @@ describe('usePopup', () => { }); it('should close popup', () => { + const initialProps = {}; const { result } = renderHook( - () => usePopup(SimplePopupComponent, {}, group), + () => usePopup(SimplePopupComponent, initialProps, group), { wrapper: TestHookWrapper, } @@ -65,12 +67,13 @@ describe('usePopup', () => { expect(() => screen.getByText('simple popup')).toThrow(); }); - it('should update popup', () => { + it('should reopen popup with new props', () => { const initialMessage = 'initial message'; const updatedMessage = 'updated message'; + const initialProps = {}; const { result } = renderHook( - () => usePopup(CustomizablePopupComponent, {}, group), + () => usePopup(CustomizablePopupComponent, initialProps, group), { wrapper: TestHookWrapper } ); @@ -90,8 +93,9 @@ describe('usePopup', () => { }); it('should merge props', () => { + const initialProps = { prop1: 42 }; const { result } = renderHook( - () => usePopup(PopupComponentWithProps, { prop1: 42 }, group), + () => usePopup(PopupComponentWithProps, initialProps, group), { wrapper: TestHookWrapper, } @@ -108,4 +112,29 @@ describe('usePopup', () => { prop2: 'hello', }); }); + + it('should update popup when props changing', () => { + const { result, rerender } = renderHook( + (props: { message: string }) => + usePopup(CustomizablePopupComponent, props, group), + { + wrapper: TestHookWrapper, + initialProps: { + message: 'initial', + }, + } + ); + + act(() => { + result.current[0](); + }); + + expect(screen.getByText('initial')).toBeDefined(); + + rerender({ + message: 'updated', + }); + + expect(screen.getByText('updated')).toBeDefined(); + }); });