diff --git a/src/core/store.ts b/src/core/store.ts index b20c9c4..7868640 100644 --- a/src/core/store.ts +++ b/src/core/store.ts @@ -1,10 +1,15 @@ import React from 'react'; import { Toast, ToastsOptions } from './types'; +export const TOAST_EXPIRE_DISMISS_DELAY = 1000; +const TOAST_LIMIT = 3; + type ActionType = { ADD_TOAST: 'ADD_TOAST'; REMOVE_TOAST: 'REMOVE_TOAST'; DISMISS_TOAST: 'DISMISS_TOAST'; + UPSERT_TOAST: 'UPSERT_TOAST'; + UPDATE_TOAST: 'UPDATE_TOAST'; PAUSE: 'PAUSE'; RESUME: 'RESUME'; }; @@ -22,6 +27,14 @@ type Action = toastId?: Toast['id']; type: ActionType['REMOVE_TOAST']; } + | { + type: ActionType['UPSERT_TOAST']; + toast: Toast; + } + | { + type: ActionType['UPDATE_TOAST']; + toast: Partial; + } | { type: ActionType['PAUSE']; time: number; @@ -39,9 +52,6 @@ interface State { const toastTimeouts = new Map>(); -export const TOAST_EXPIRE_DISMISS_DELAY = 0; -const TOAST_LIMIT = 3; - const addToRemoveQueue = (toastId: string) => { if (toastTimeouts.has(toastId)) { return; @@ -58,6 +68,13 @@ const addToRemoveQueue = (toastId: string) => { toastTimeouts.set(toastId, timeout); }; +const clearFromRemoveQueue = (toastId: string) => { + const timeout = toastTimeouts.get(toastId); + if (timeout) { + clearTimeout(timeout); + } +}; + export const reducer = (state: State, action: Action): State => { switch (action.type) { case 'ADD_TOAST': { @@ -70,7 +87,6 @@ export const reducer = (state: State, action: Action): State => { case 'DISMISS_TOAST': { const { toastId } = action; - // ! Side effects ! - This could be execrated into a dismissToast() action, but I'll keep it here for simplicity if (toastId) { addToRemoveQueue(toastId); } else { @@ -107,6 +123,25 @@ export const reducer = (state: State, action: Action): State => { }; } + case 'UPSERT_TOAST': + const { toast } = action; + return state.toasts.find((t) => t.id === toast.id) + ? reducer(state, { type: 'UPDATE_TOAST', toast }) + : reducer(state, { type: 'ADD_TOAST', toast }); + + case 'UPDATE_TOAST': { + if (action.toast.id) { + clearFromRemoveQueue(action.toast.id); + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + }; + } + case 'PAUSE': { return { ...state, diff --git a/src/core/use-toast.ts b/src/core/use-toast.ts index fd559bc..9410653 100644 --- a/src/core/use-toast.ts +++ b/src/core/use-toast.ts @@ -4,6 +4,7 @@ import { dispatch, useStore } from './store'; import { Toast, ToastArg, + ToastOptions, ToasterType, ToastsOptions, isFunction, @@ -47,7 +48,7 @@ function createHandler(type?: ToasterType) { return (options: ToastArg) => { const toast = createToast(type, options); dispatch({ - type: 'ADD_TOAST', + type: 'UPSERT_TOAST', toast, }); return toast.id; @@ -59,6 +60,26 @@ toast.error = createHandler('error'); toast.success = createHandler('success'); toast.loading = createHandler('loading'); +toast.promise = ( + promise: Promise, + content: { + loading: ToastOptions; + success: ToastOptions; + error: ToastOptions; + } +) => { + const id = toast.loading(content.loading); + promise + .then((p) => { + toast.success({ ...content.success, id }); + return p; + }) + .catch(() => { + toast.error({ ...content.error, id }); + }); + return promise; +}; + toast.dismiss = (toastId?: string) => { dispatch({ type: 'DISMISS_TOAST',