1 | import React, { createContext, useContext, useReducer } from 'react';
|
2 | import withLogger from '../util/withLogger';
|
3 |
|
4 | /**
|
5 | * The current state of the toast store.
|
6 | *
|
7 | * @typedef {Object} ToastState
|
8 | *
|
9 | * @property {Map} toasts Map object associating an id to toast data
|
10 | */
|
11 | const initialState = {
|
12 | toasts: new Map()
|
13 | };
|
14 |
|
15 | const reducer = (prevState = initialState, action = {}) => {
|
16 | const { type, payload } = action;
|
17 |
|
18 | switch (type) {
|
19 | case 'add': {
|
20 | const nextToasts = new Map(prevState.toasts);
|
21 | const prevToast = nextToasts.get(payload.id);
|
22 |
|
23 | const isDuplicate = !!prevToast;
|
24 | let timestamp = payload.timestamp;
|
25 | if (isDuplicate) {
|
26 | // If this is a _new_ duplicate toast we need to clear the
|
27 | // previous timeout to prevent premature removal.
|
28 | window.clearTimeout(prevToast.removalTimeoutId);
|
29 |
|
30 | // And to retain chronological order of addition, keep the
|
31 | // original timestamp.
|
32 | timestamp = prevToast.timestamp;
|
33 | }
|
34 |
|
35 | nextToasts.set(payload.id, {
|
36 | ...payload,
|
37 | timestamp,
|
38 | isDuplicate
|
39 | });
|
40 |
|
41 | return {
|
42 | ...prevState,
|
43 | toasts: nextToasts
|
44 | };
|
45 | }
|
46 | case 'remove': {
|
47 | const nextToasts = new Map(prevState.toasts);
|
48 |
|
49 | const prevToast = nextToasts.get(payload.id);
|
50 | if (prevToast) {
|
51 | window.clearTimeout(prevToast.removalTimeoutId);
|
52 | }
|
53 |
|
54 | nextToasts.delete(payload.id);
|
55 |
|
56 | return {
|
57 | ...prevState,
|
58 | toasts: nextToasts
|
59 | };
|
60 | }
|
61 | default:
|
62 | return prevState;
|
63 | }
|
64 | };
|
65 |
|
66 | const ToastContext = createContext();
|
67 |
|
68 | const wrappedReducer = withLogger(reducer);
|
69 |
|
70 | /**
|
71 | * A [context]{@link https://reactjs.org/docs/context.html} provider that
|
72 | * provides the toast state object and a dispatch function to toast
|
73 | * functionality consumers.
|
74 | *
|
75 | * @typedef ToastContextProvider
|
76 | *
|
77 | */
|
78 | export const ToastContextProvider = ({ children }) => {
|
79 | const store = useReducer(wrappedReducer, initialState);
|
80 | return (
|
81 | <ToastContext.Provider value={store}>{children}</ToastContext.Provider>
|
82 | );
|
83 | };
|
84 |
|
85 | /**
|
86 | * A hook that provides access to the toast state and dispatch.
|
87 | * Any component using this hook _must_ be a child of a {@link ToastContextProvider}.
|
88 | *
|
89 | * @typedef useToastContext
|
90 | *
|
91 | * @return {Object[]} An array containing the state and dispatch function: [{@link ToastState}, function]
|
92 | *
|
93 | * @example
|
94 | * const [toastState, dispatch] = useToastState();
|
95 | */
|
96 | export const useToastContext = () => useContext(ToastContext);
|