-
Notifications
You must be signed in to change notification settings - Fork 0
/
react-hands-v2.jsx
executable file
·142 lines (119 loc) · 3.86 KB
/
react-hands-v2.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"use client";
import React, {
createContext,
useReducer,
useMemo,
useContext,
useEffect,
useState,
} from "react";
const StoreContext = createContext();
const isClient = typeof window !== "undefined";
const hasLocalStorage = typeof localStorage !== "undefined";
export function useStore() {
const store = useContext(StoreContext);
if (!store) {
throw new Error(
"React Hands: The useStore hook must be used within a StoreProvider. Wrap your top level component or app in a react hands store provider!"
);
}
return [store.state, store.dispatch];
}
export function storeConfig(initialState = {}, actions = {}) {
// Add default __HYDRATE__ action
const defaultActions = {
__HYDRATE__: (state, action) => ({
...state,
...action.payload,
}),
...actions,
};
function reducer(state, action) {
const handler = defaultActions[action.action];
if (handler) {
const prevState = { ...state }; // Track previous state
try {
const nextState = handler(state, action);
// Check if state key should be persisted
Object.keys(nextState).forEach((key) => {
if (key.startsWith("local_") && hasLocalStorage) {
localStorage.setItem(key, JSON.stringify(nextState[key]));
}
});
return { ...prevState, ...nextState };
} catch (error) {
console.error(error);
// Revert back to previous state in case of an error
return prevState;
}
} else {
console.error(
`React Hands: The action "${action.action}" is not defined in store config actions`
);
return state;
}
}
function StoreProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
const [isHydrated, setIsHydrated] = useState(false);
useEffect(() => {
if (isClient && hasLocalStorage && !isHydrated) {
const persistedState = {};
Object.keys(initialState).forEach((key) => {
if (key.startsWith("local_")) {
const value = localStorage.getItem(key);
if (value !== null) {
persistedState[key] = JSON.parse(value);
}
}
});
// fixes server side rendering hydration error
dispatch({ action: "__HYDRATE__", payload: persistedState });
setIsHydrated(true);
}
}, []);
useEffect(() => {
if (isClient && hasLocalStorage && isHydrated) {
Object.keys(state).forEach((key) => {
if (key.startsWith("local_")) {
localStorage.setItem(key, JSON.stringify(state[key]));
}
});
}
}, [state]);
const store = useMemo(() => ({ state, dispatch }), [state, dispatch]);
return (
<StoreContext.Provider value={store}>
<ErrorBoundary>{children}</ErrorBoundary>
</StoreContext.Provider>
);
}
return StoreProvider;
}
// * Additional Utilities
function ErrorBoundary({ children }) {
const [error, setError] = useState(false);
if (error) {
// Render the fallback UI with the previous state
return <>{children}</>;
}
return (
<ErrorBoundaryWrapper onCatch={() => setError(true)}>
{children}
</ErrorBoundaryWrapper>
);
}
// Todo: address warning - sonarlint(javascript:S6478) during performance adjustments phase
function ErrorBoundaryWrapper({ children, onCatch }) {
class ErrorBoundaryComponent extends React.Component {
componentDidCatch(error, errorInfo) {
// Log the error to the error tracking service or console
console.error(error, errorInfo);
onCatch(); // Trigger error state update
}
render() {
return this.props.children;
}
}
return <ErrorBoundaryComponent>{children}</ErrorBoundaryComponent>;
}