main
tsx 119 lines 2.75 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import type {Dispatch, ReactNode} from 'react';
9 import {useState, useEffect, useReducer} from 'react';
10 import createContext from '../lib/createContext';
11 import {emptyStore, defaultStore} from '../lib/defaultStore';
12 import {
13 saveStore,
14 initStoreFromUrlOrLocalStorage,
15 type Store,
16 } from '../lib/stores';
17
18 const StoreContext = createContext<Store>();
19
20 /**
21 * Hook to access the store.
22 */
23 export const useStore = StoreContext.useContext;
24
25 const StoreDispatchContext = createContext<Dispatch<ReducerAction>>();
26
27 /**
28 * Hook to access the store dispatch function.
29 */
30 export const useStoreDispatch = StoreDispatchContext.useContext;
31
32 /**
33 * Make Store and dispatch function available to all sub-components in children.
34 */
35 export function StoreProvider({children}: {children: ReactNode}): JSX.Element {
36 const [store, dispatch] = useReducer(storeReducer, emptyStore);
37 const [isPageReady, setIsPageReady] = useState<boolean>(false);
38
39 useEffect(() => {
40 let mountStore: Store;
41 try {
42 mountStore = initStoreFromUrlOrLocalStorage();
43 } catch (e) {
44 console.error('Failed to initialize store from URL or local storage', e);
45 mountStore = defaultStore;
46 }
47 dispatch({type: 'setStore', payload: {store: mountStore}});
48 setIsPageReady(true);
49 }, []);
50
51 useEffect(() => {
52 if (store !== emptyStore) {
53 saveStore(store);
54 }
55 }, [store]);
56
57 return (
58 <StoreContext.Provider value={store}>
59 <StoreDispatchContext.Provider value={dispatch}>
60 {isPageReady ? children : null}
61 </StoreDispatchContext.Provider>
62 </StoreContext.Provider>
63 );
64 }
65
66 type ReducerAction =
67 | {
68 type: 'setStore';
69 payload: {
70 store: Store;
71 };
72 }
73 | {
74 type: 'updateSource';
75 payload: {
76 source: string;
77 };
78 }
79 | {
80 type: 'updateConfig';
81 payload: {
82 config: string;
83 };
84 }
85 | {
86 type: 'toggleInternals';
87 };
88
89 function storeReducer(store: Store, action: ReducerAction): Store {
90 switch (action.type) {
91 case 'setStore': {
92 const newStore = action.payload.store;
93 return newStore;
94 }
95 case 'updateSource': {
96 const source = action.payload.source;
97 const newStore = {
98 ...store,
99 source,
100 };
101 return newStore;
102 }
103 case 'updateConfig': {
104 const config = action.payload.config;
105 const newStore = {
106 ...store,
107 config,
108 };
109 return newStore;
110 }
111 case 'toggleInternals': {
112 const newStore = {
113 ...store,
114 showInternals: !store.showInternals,
115 };
116 return newStore;
117 }
118 }
119 }