Skip to content

Commit

Permalink
fix(useModal): add type safe actions to useModal hook (#20)
Browse files Browse the repository at this point in the history
* Enhance useModal

* Fix prettier error
  • Loading branch information
carlosthe19916 authored Dec 29, 2021
1 parent 70ba353 commit c72d07c
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 19 deletions.
18 changes: 11 additions & 7 deletions src/hooks/useModal/useModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,52 @@ import { useModal } from './useModal';

describe('useModal', () => {
it('onOpen: without data', () => {
const { result } = renderHook(() => useModal());
const { result } = renderHook(() => useModal<'edit'>());

// Open modal
const { open } = result.current;
act(() => open('edit'));
expect(result.current.isOpen).toEqual(true);
expect(result.current.data).toBeUndefined();
expect(result.current.actionKey).toEqual('edit');
expect(result.current.action).toEqual('edit');
expect(result.current.isAction('edit')).toEqual(true);
});

it('onOpen: with data', () => {
const ENTITY = 'hello';

const { result } = renderHook(() => useModal<string>());
const { result } = renderHook(() => useModal<'edit'>());

// Open modal
const { open } = result.current;
act(() => open('edit', ENTITY));

expect(result.current.isOpen).toEqual(true);
expect(result.current.data).toEqual(ENTITY);
expect(result.current.actionKey).toEqual('edit');
expect(result.current.action).toEqual('edit');
expect(result.current.isAction('edit')).toEqual(true);
});

it('Close modal with data', () => {
const ENTITY = 'hello';

const { result } = renderHook(() => useModal<string>());
const { result } = renderHook(() => useModal<'edit'>());
const { open, close } = result.current;

// Open modal
act(() => open('edit', ENTITY));

expect(result.current.isOpen).toEqual(true);
expect(result.current.data).toEqual(ENTITY);
expect(result.current.actionKey).toEqual('edit');
expect(result.current.action).toEqual('edit');
expect(result.current.isAction('edit')).toEqual(true);

// Close modal
act(() => close());

expect(result.current.isOpen).toEqual(false);
expect(result.current.data).toBeUndefined();
expect(result.current.actionKey).toBeUndefined();
expect(result.current.action).toBeUndefined();
expect(result.current.isAction('edit')).toEqual(false);
});
});
26 changes: 14 additions & 12 deletions src/hooks/useModal/useModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@ import { useCallback, useReducer } from 'react';
import { ActionType, createAction, getType } from 'typesafe-actions';

interface IOpenAction {
action: any;
data: any;
actionKey: string;
}

const openModal = createAction('useModal/action/openModalWithData')<IOpenAction>();
const closeModal = createAction('useModal/action/startClose')();

// State
type State = Readonly<{
action: any;
data: any;
isOpen: boolean;
actionKey?: string;
}>;

const defaultState: State = {
action: undefined,
data: undefined,
isOpen: false,
actionKey: undefined,
};

// Reducer
Expand All @@ -31,16 +31,16 @@ const reducer = (state: State, action: Action): State => {
case getType(openModal):
return {
...state,
action: action.payload.action,
data: action.payload.data,
isOpen: true,
actionKey: action.payload.actionKey,
};
case getType(closeModal):
return {
...state,
action: undefined,
data: undefined,
isOpen: false,
actionKey: undefined,
};
default:
return state;
Expand All @@ -49,33 +49,35 @@ const reducer = (state: State, action: Action): State => {

// Hook

interface HookState<T> {
interface HookState<A, T> {
action?: A;
data?: T;
isOpen: boolean;
actionKey?: string;
open: (actionKey: string, data?: T) => void;
open: (action: A, data?: T) => void;
close: () => void;
isAction: (action: A) => boolean;
}

export const useModal = <T>(): HookState<T> => {
export const useModal = <A, T = any>(): HookState<A, T> => {
const [state, dispatch] = useReducer(reducer, {
...defaultState,
});

const openHandler = useCallback((actionKey: string, entity?: T) => {
dispatch(openModal({ actionKey, data: entity }));
const openHandler = useCallback((action: A, entity?: T) => {
dispatch(openModal({ action: action, data: entity }));
}, []);

const closeHandler = useCallback(() => {
dispatch(closeModal());
}, []);

return {
action: state.action,
data: state.data,
isOpen: state.isOpen,
actionKey: state.actionKey,
open: openHandler,
close: closeHandler,
isAction: (action: A) => state.action === action,
};
};

Expand Down

0 comments on commit c72d07c

Please sign in to comment.