import isEqual from 'lodash/isEqual';
import { useEffect, useCallback, useContext } from 'react';
import { globalContext } from '../state/ModalStoreProvider';
import { SET_MODAL_STATE } from '../state/modalReducer';
export type BasicState<S> = S & {
isOpened?: boolean;
};
interface CreateSetterActionOutput<T> {
name: string;
state: T;
}
interface CreateSetterAction<T> {
type: string;
payload: CreateSetterActionOutput<T>;
}
interface UseModalStateReturn<T> {
isOpened: boolean;
state: T;
close: () => void;
open: () => void;
set: (value: T) => void;
reset: () => void;
setAndOpen: (value: T) => void;
resetAndClose: () => void;
}
/**
* A hook to access the modals' state. This hook takes n modal name
* as a first argument
*
* This hook takes an initial state as the second optional argument
* that allows you to set initial data for the specific modal's state based on the modal name argument
*
*
* @param name modal name
* @param initialState initialState for the specific modal
*
* @returns the modal's state and helper functions
*
* @example
*
* import React from "react";
* import { useModalState, ModalStoreProvider } from "react-use-modal-state";
*
* const Counter = () => {
* const {
* isOpened,
* state: { counter },
* set,
* open,
* close
* } = useModalState("EXAMPLE_MODAL", { counter: 1, isOpened: true });
*
* return (
* <div>
* {isOpened && <div>{counter}</div>}
* <button onClick={() => set({ counter: counter + 1 })}>increment</button>
* <button onClick={() => (isOpened ? close() : open())}>
* {isOpened ? "Hide counter" : "Show counter"}
* </button>
* </div>
* );
* };
*
* const App = () => (
* <>
* <ModalStoreProvider>
* <Counter />
* </ModalStoreProvider>
* </>
* );
*
* export default App;
*/
export const useModalState = <T>(
name: string,
initialState = {} as BasicState<T>
): UseModalStateReturn<T> => {
const { state: currentModalState, dispatch } = useContext<any>(globalContext);
const state = currentModalState?.[name];
const createSetterAction = useCallback(
(payload: T): CreateSetterAction<T> => ({
type: SET_MODAL_STATE,
payload: { state: payload as T, name },
}),
[name]
);
useEffect(() => {
if (!state) {
const newModalState = { isOpened: false, ...initialState };
dispatch(createSetterAction(newModalState));
}
}, [createSetterAction, dispatch, initialState, state]);
const close = (): void => {
if (state?.isOpened) {
dispatch(createSetterAction({ ...state, isOpened: false }));
}
};
const open = (): void => {
if (!state?.isOpened) {
dispatch(createSetterAction({ ...state, isOpened: true }));
}
};
const set = (value: T): void => {
const oldState = state;
const newState = { ...state, ...value };
if (!isEqual(oldState, newState)) {
dispatch(createSetterAction(newState));
}
};
const reset = (): void => {
dispatch(createSetterAction({ ...state, ...initialState, isOpened: false }));
};
const setAndOpen = (value: T): void => {
set({ ...value, isOpened: true });
};
const resetAndClose = (): void => {
if (state?.isOpened) {
dispatch(createSetterAction({ ...initialState, isOpened: false }));
}
};
const output = {
isOpened: state?.isOpened ?? false,
state: { ...initialState, ...state } as T,
close,
open,
set,
setAndOpen,
reset,
resetAndClose,
};
return output;
};
Source