diff --git a/package.json b/package.json index 663a373..5ef1834 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "@patternfly/react-tokens": "^4.11.2", "axios": "^0.21.1", "react": "^17.0.2", - "react-dom": "^17.0.2" + "react-dom": "^17.0.2", + "typesafe-actions": "^5.1.0" }, "devDependencies": { "@babel/core": "^7.13.16", @@ -83,6 +84,7 @@ "semantic-release": "^17.4.2", "ts-jest": "^26.5.5", "tslib": "^2.2.0", + "typesafe-actions": "^5.1.0", "typescript": "^4.2.4" }, "config": { diff --git a/src/hooks/useModal/index.ts b/src/hooks/useModal/index.ts new file mode 100644 index 0000000..12d5460 --- /dev/null +++ b/src/hooks/useModal/index.ts @@ -0,0 +1 @@ +export { useModal } from './useModal'; diff --git a/src/hooks/useModal/useModal.test.tsx b/src/hooks/useModal/useModal.test.tsx new file mode 100644 index 0000000..afff793 --- /dev/null +++ b/src/hooks/useModal/useModal.test.tsx @@ -0,0 +1,46 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useModal } from './useModal'; + +describe('useModal', () => { + it('onOpen: without data', () => { + const { result } = renderHook(() => useModal()); + + // Open modal + const { open } = result.current; + act(() => open()); + expect(result.current.isOpen).toEqual(true); + expect(result.current.data).toBeUndefined(); + }); + + it('onOpen: with data', () => { + const ENTITY = 'hello'; + + const { result } = renderHook(() => useModal()); + + // Open modal + const { open } = result.current; + act(() => open(ENTITY)); + + expect(result.current.isOpen).toEqual(true); + expect(result.current.data).toEqual(ENTITY); + }); + + it('Close modal with data', () => { + const ENTITY = 'hello'; + + const { result } = renderHook(() => useModal()); + const { open, close } = result.current; + + // Open modal + act(() => open(ENTITY)); + + expect(result.current.isOpen).toEqual(true); + expect(result.current.data).toEqual(ENTITY); + + // Close modal + act(() => close()); + + expect(result.current.isOpen).toEqual(false); + expect(result.current.data).toBeUndefined(); + }); +}); diff --git a/src/hooks/useModal/useModal.ts b/src/hooks/useModal/useModal.ts new file mode 100644 index 0000000..5a73496 --- /dev/null +++ b/src/hooks/useModal/useModal.ts @@ -0,0 +1,71 @@ +import { useCallback, useReducer } from 'react'; +import { ActionType, createAction, getType } from 'typesafe-actions'; + +const openModal = createAction('useModal/action/openModalWithData')(); +const closeModal = createAction('useModal/action/startClose')(); + +// State +type State = Readonly<{ + data: any; + isOpen: boolean; +}>; + +const defaultState: State = { + data: undefined, + isOpen: false, +}; + +// Reducer + +type Action = ActionType; + +const reducer = (state: State, action: Action): State => { + switch (action.type) { + case getType(openModal): + return { + ...state, + data: action.payload, + isOpen: true, + }; + case getType(closeModal): + return { + ...state, + data: undefined, + isOpen: false, + }; + default: + return state; + } +}; + +// Hook + +interface HookState { + data?: T; + isOpen: boolean; + open: (data?: T) => void; + close: () => void; +} + +export const useModal = (): HookState => { + const [state, dispatch] = useReducer(reducer, { + ...defaultState, + }); + + const openHandler = useCallback((entity?: T) => { + dispatch(openModal(entity)); + }, []); + + const closeHandler = useCallback(() => { + dispatch(closeModal()); + }, []); + + return { + data: state.data, + isOpen: state.isOpen, + open: openHandler, + close: closeHandler, + }; +}; + +export default useModal; diff --git a/src/index.ts b/src/index.ts index a9cbe79..644dad6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ export * from './components/StatusIcon'; -export * from './hooks/useDelete'; \ No newline at end of file +export * from './hooks/useDelete'; +export * from './hooks/useModal'; diff --git a/yarn.lock b/yarn.lock index 1652240..ae31598 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14481,6 +14481,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typesafe-actions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/typesafe-actions/-/typesafe-actions-5.1.0.tgz#9afe8b1e6a323af1fd59e6a57b11b7dd6623d2f1" + integrity sha512-bna6Yi1pRznoo6Bz1cE6btB/Yy8Xywytyfrzu/wc+NFW3ZF0I+2iCGImhBsoYYCOWuICtRO4yHcnDlzgo1AdNg== + typescript@^4.2.4, typescript@^4.4.3: version "4.4.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"