├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── example
├── index.html
├── index.tsx
└── react-dev-tools.png
├── package.json
├── src
└── index.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | example/dist
4 | dist
5 | .rts2_cache_*
6 | .cache
7 | .idea
8 | .vscode
9 | yarn.lock
10 | /.parcel-cache/
11 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .rts2_cache_*
2 | node_modules
3 | .*ignore
4 | dist
5 | LICENSE
6 | yarn.lock
7 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "trailingComma": "all",
4 | "overrides": [
5 | {
6 | "files": "*.md",
7 | "options": {
8 | "useTabs": false
9 | }
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | English
4 |
5 |
6 | (Please contribute translations!)
7 |
8 |
9 | # Use Global State Hook
10 |
11 | > Super tiny state sharing library with hooks
12 |
13 | ## Install
14 |
15 | ```sh
16 | npm install --save global-state-hook
17 | ```
18 |
19 | ## Update 2023:
20 | #### With React 18 support useSyncExternalStore, you can now write your React App like that:
21 |
22 | ```typescript jsx
23 | let textStore3 = createSubscription({ value: "Text 3", value2: "Text 4" })
24 | function Text3() {
25 | let { state, subscription } = useSyncStore(textStore3)
26 | return (
27 |
28 | subscription.updateState({ value: e.target.value })}
31 | />
32 |
33 | )
34 | }
35 | function Text4() {
36 | let { state, subscription } = useSyncStore(textStore3)
37 | return (
38 |
39 | subscription.updateState({ value2: e.target.value })}
42 | />
43 |
44 | )
45 | }
46 | ```
47 |
48 | ## Update 2022:
49 |
50 | #### With new Reactive pattern, you can now write your React App like that:
51 |
52 | ```typescript jsx
53 | const sourceOfTruth = createReactive({
54 | text1: "Text 1 sync together",
55 | text2: "Text 2 walk alone.",
56 | })
57 | const Text1 = () => {
58 | const state = useReactive(sourceOfTruth, ["text1"])
59 | return (
60 | (state.text1 = e.target.value)}
63 | />
64 | )
65 | }
66 | const Text2 = () => {
67 | const state = useReactive(sourceOfTruth, ["text2"])
68 | return (
69 | (state.text2 = e.target.value)}
72 | />
73 | )
74 | }
75 | const ReactiveApp = () => {
76 | return (
77 |
78 |
Reactive pattern:
79 |
80 |
81 |
82 |
83 | )
84 | }
85 | ```
86 |
87 | ## Example
88 |
89 | [](https://codesandbox.io/s/bold-ellis-6rg1t?fontsize=14&hidenavigation=1&theme=dark)
90 |
91 | ```jsx harmony
92 | import React from "react"
93 | import { createSubscription, useSubscription } from "global-state-hook"
94 | import { render } from "react-dom"
95 |
96 | const counterSubscription = createSubscription({ count: 0, foo: 10 })
97 |
98 | function CounterDisplay() {
99 | let { state, setState } = useSubscription(counterSubscription)
100 | return (
101 |
102 | setState({ count: state.count - 1 })}>-
103 | {state.count}
104 | setState({ count: state.count + 1 })}>+
105 |
106 | )
107 | }
108 | function FooDisplay() {
109 | // Only update when foo change
110 | let { state, setState } = useSubscription(counterSubscription, ["foo"])
111 | return (
112 |
113 | setState({ foo: state.foo - 1 })}>-
114 | {state.foo}
115 | setState({ foo: state.foo + 1 })}>+
116 |
117 | )
118 | }
119 |
120 | function App() {
121 | return (
122 | <>
123 |
124 |
125 | >
126 | )
127 | }
128 |
129 | render( , document.getElementById("root"))
130 | ```
131 |
132 | ## API
133 |
134 | ### `createSubscription(initialState)`
135 |
136 | ```js
137 | import { createSubscription } from "global-state-hook"
138 |
139 | const counterSubscription = createSubscription({ count: 0 })
140 | ```
141 |
142 | ### `useSubscription(subscriber)`
143 |
144 | ```js
145 | import { createSubscription, useSubscription } from "global-state-hook"
146 |
147 | const counterSubscription = createSubscription({ count: 0 })
148 |
149 | // Your custom hook goes here so you can share it to anywhere
150 | const useCounter = () => {
151 | let { state, setState } = useSubscription(counterSubscription)
152 | const increment = () => setState({ count: state.count + 1 })
153 | const decrement = () => setState({ count: state.count + 1 })
154 | return { count: state.count, increment, decrement }
155 | }
156 |
157 | function Counter() {
158 | let { count, increment, decrement } = useCounter()
159 | return (
160 |
161 | -
162 | {count}
163 | +
164 |
165 | )
166 | }
167 | ```
168 |
169 | ### `useSubscription(subscriber, pick: string[])`
170 |
171 | ```js
172 | import { createSubscription, useSubscription } from "global-state-hook"
173 |
174 | const counterSubscription = createSubscription({ count: 0, foo: 10 })
175 |
176 | function Foo() {
177 | // Only update when foo change
178 | let { state, setState } = useSubscription(counterSubscription, ["foo"])
179 |
180 | return (
181 |
182 | setState({ foo: state.foo - 1 })}>-
183 | {state.foo}
184 | setState({ foo: state.foo + 1 })}>+
185 |
186 | )
187 | }
188 | ```
189 |
190 | ## Why use global-state-hook?
191 |
192 | **It's minimal** You only need to learn 2 API: createSubscription, useSubscription.
193 |
194 | **It's easy to integrate** Can integrate with any React library.
195 |
196 | **It's small** Only 50 lines of code for this library.
197 |
198 | ## Guide
199 |
200 | ### Global state is not bad.
201 |
202 | It's about how you manage it, global state will not be bad if you do it the right way.
203 | You can consider using `Context.Provider` to provide your global subscription so it can be clean up after the component unmounted. Example:
204 |
205 | ```js
206 | const TextContext = React.createContext < any > null
207 |
208 | const useTextValue = () => {
209 | const textSubscription = useContext(TextContext)
210 | let { state, setState } = useSubscription(textSubscription)
211 | const onChange = (e) => setState({ value: e.target.value })
212 | return { value: state.value, onChange }
213 | }
214 |
215 | function Text() {
216 | let { value, onChange } = useTextValue()
217 | return (
218 |
219 |
220 |
221 | )
222 | }
223 |
224 | function TextComponent() {
225 | const textSubscription = createSubscription({
226 | value: "The text will sync together",
227 | })
228 | return (
229 |
230 |
231 |
232 | )
233 | }
234 | ```
235 |
236 | ### Tip#1: Select your state property that you want to subscribe to
237 |
238 | By default some people might recommend you to put only one state to a `subscription`, for example:
239 |
240 | ```js
241 | const textSubscription = createSubscription("Your initial state here")
242 |
243 | let { state: text, setState: setText } = useSubscription(textSubscription)
244 |
245 | // Change the text value:
246 | setText("New text value")
247 | ```
248 |
249 | But in case you have a very large Component with many state in it, so what is the proper way to handle? Just see my code below:
250 |
251 | ```js
252 | import { createSubscription, useSubscription } from "global-state-hook"
253 |
254 | const multiStateSubscription = createSubscription({ foo: 1, bar: 2, baz: 3 })
255 |
256 | function Foo() {
257 | // Only update when foo or baz change
258 | let { state, setState } = useSubscription(multiStateSubscription, [
259 | "foo",
260 | "baz",
261 | ])
262 |
263 | return (
264 |
265 | setState({ foo: state.foo - 1 })}>-
266 | {state.foo}
267 | {state.baz}
268 | setState({ foo: state.foo + 1 })}>+
269 |
270 | )
271 | }
272 | ```
273 |
274 | ### Reducer pattern:
275 |
276 | For those who still in love with redux, the reducer pattern will fit for you:
277 |
278 | ```js
279 | const counterSubscription = createSubscription({ count: 0 })
280 | function reducer(state, action) {
281 | switch (action.type) {
282 | case "increment":
283 | return { count: state.count + 1 }
284 | case "decrement":
285 | return { count: state.count - 1 }
286 | default:
287 | throw new Error()
288 | }
289 | }
290 |
291 | function Counter() {
292 | const { state, dispatch } = useReducerSubscription(
293 | counterSubscription,
294 | reducer,
295 | )
296 | return (
297 | <>
298 | Count: {state.count}
299 | dispatch({ type: "decrement" })}>-
300 | dispatch({ type: "increment" })}>+
301 | >
302 | )
303 | }
304 | ```
305 |
306 | ### Misc
307 |
308 | # Support debugging with React Developer Tools
309 |
310 | 
311 |
312 | It's so easy right? :D
313 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/index.tsx:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import React, { ChangeEvent, useContext, useEffect } from "react"
3 | import {
4 | createReactive,
5 | createSubscription,
6 | ISubscription,
7 | useReactive,
8 | useReducerSubscription,
9 | useSubscription,
10 | useSyncStore,
11 | } from "../src/index"
12 | import { createRoot } from "react-dom/client"
13 |
14 | const counterSubscription = createSubscription({ count: 0, foo: 10 })
15 |
16 | const useCounter = () => {
17 | let { state, subscription } = useSyncStore(counterSubscription)
18 | const increment = () => subscription.updateState({ count: state.count + 1 })
19 | const decrement = () => subscription.updateState({ count: state.count - 1 })
20 | return { count: state.count, increment, decrement }
21 | }
22 |
23 | function CounterDisplay() {
24 | let { count, increment, decrement } = useCounter()
25 | return (
26 |
27 | -
28 | {count}
29 | +
30 |
31 | )
32 | }
33 | let CounterDisplay2 = () => {
34 | let { state, subscription } = useSyncStore(counterSubscription)
35 | return (
36 |
37 | Counter syncing with useSyncStore store: {state.count}
38 | subscription.updateState({ count: 0 })}>
39 | Reset
40 |
41 |
42 | )
43 | }
44 | function FooDisplay() {
45 | // Only update when foo change
46 | let { state, subscription } = useSyncStore(counterSubscription)
47 |
48 | console.log("Only update when foo change", state)
49 | return (
50 |
51 | Only update when foo change
52 | subscription.updateState({ foo: state.foo - 1 })}>
53 | -
54 |
55 | {state.foo}
56 | subscription.updateState({ foo: state.foo + 1 })}>
57 | +
58 |
59 |
60 | )
61 | }
62 |
63 | const useTextValue = () => {
64 | const textSubscription = useContext>(TextContext)
65 | let { state, setState } = useSubscription(textSubscription)
66 | const onChange = (e: ChangeEvent) =>
67 | setState(e.target.value)
68 | return { value: state, onChange }
69 | }
70 |
71 | function Text() {
72 | let { value, onChange } = useTextValue()
73 | return (
74 |
75 |
76 |
77 | )
78 | }
79 |
80 | const TextContext = React.createContext>(null)
81 |
82 | // function TextComponent() {
83 | // const textSubscription = createSubscription("The text will sync together")
84 | // return (
85 | //
86 | //
87 | //
88 | //
89 | // )
90 | // }
91 |
92 | const fakeData = [
93 | {
94 | name: "Minh",
95 | email: "phanminh65@gmail.com",
96 | },
97 | {
98 | name: "Tester 1",
99 | email: "tester1@gmail.com",
100 | },
101 | ]
102 |
103 | const mounterSubscription = createSubscription({ display: false, data: [] })
104 |
105 | function DelayFetch() {
106 | const { setState, state } = useSubscription(mounterSubscription)
107 | useEffect(() => {
108 | console.log(
109 | "fetching... it will stop update if component is unmount but the state will still be changed",
110 | )
111 | setTimeout(() => {
112 | setState({ data: fakeData }, () => {
113 | console.log("fetch data done...", state)
114 | })
115 | }, 5000)
116 | }, [state.display])
117 |
118 | return (
119 |
120 | {state.data.map((d: any) => {
121 | return (
122 |
123 | {d.name}
124 | {d.email}
125 |
126 | )
127 | })}
128 |
129 | )
130 | }
131 |
132 | function MountAndUnmount() {
133 | const { state, setState } = useSubscription(mounterSubscription)
134 |
135 | // If you unmount when data is fetching
136 | return (
137 |
138 | setState({ display: !state.display })}>
139 | {state.display ? "Unmount" : "Mount"}
140 |
141 | {state.display && }
142 |
143 | )
144 | }
145 |
146 | const initialState = { count: 0 }
147 | const counter2Sub = createSubscription(initialState)
148 | function reducer(state: any, action: any) {
149 | switch (action.type) {
150 | case "increment":
151 | return { count: state.count + 1 }
152 | case "decrement":
153 | return { count: state.count - 1 }
154 | default:
155 | throw new Error()
156 | }
157 | }
158 |
159 | function Counter2() {
160 | const { state, dispatch } = useReducerSubscription(counter2Sub, reducer)
161 | return (
162 | <>
163 | Count: {state.count}
164 | dispatch({ type: "decrement" })}>-
165 | dispatch({ type: "increment" })}>+
166 | >
167 | )
168 | }
169 |
170 | const sourceOfTruth = createReactive({
171 | text1: "Text 1 sync together",
172 | text2: "Text 2 walk alone.",
173 | })
174 | const Text1 = () => {
175 | const state = useReactive(sourceOfTruth, ["text1"])
176 | return (
177 | (state.text1 = e.target.value)}
180 | />
181 | )
182 | }
183 | const Text2 = () => {
184 | const state = useReactive(sourceOfTruth, ["text2"])
185 | return (
186 | (state.text2 = e.target.value)}
189 | />
190 | )
191 | }
192 | const ReactiveApp = () => {
193 | return (
194 |
195 |
Reactive pattern:
196 |
197 |
198 |
199 |
200 | )
201 | }
202 |
203 | let textStore3 = createSubscription({ value: "Text 3", value2: "Text 4" })
204 |
205 | function Text3() {
206 | let { state, subscription } = useSyncStore(textStore3)
207 | return (
208 |
209 | subscription.updateState({ value: e.target.value })}
212 | />
213 |
214 | )
215 | }
216 |
217 | function Text4() {
218 | let { state, subscription } = useSyncStore(textStore3)
219 | return (
220 |
221 | subscription.updateState({ value2: e.target.value })}
224 | />
225 |
226 | )
227 | }
228 |
229 | function App() {
230 | return (
231 | <>
232 | {/* */}
233 | {/* */}
234 | {/*/!*a line break*!/*/}
235 | {/*
*/}
242 |
243 |
244 |
245 |
246 |
253 |
254 |
255 |
256 |
257 | {/* */}
258 |
259 | {/*
*/}
266 | {/* */}
267 | >
268 | )
269 | }
270 |
271 | let root = createRoot(document.getElementById("root"))
272 | root.render( )
273 |
--------------------------------------------------------------------------------
/example/react-dev-tools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Weaverse/global-state-hook/beb4ef75d574f6768358c34eb301bf0b364999b2/example/react-dev-tools.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "global-state-hook",
3 | "version": "3.1.0",
4 | "description": "Super tiny state sharing library with hooks",
5 | "source": "src/index.ts",
6 | "main": "dist/index.js",
7 | "module": "dist/index.mjs",
8 | "unpkg": "dist/index.umd.js",
9 | "types": "dist/index.d.ts",
10 | "files": [
11 | "dist/index.js",
12 | "dist/index.mjs",
13 | "dist/index.umd.js",
14 | "dist/index.d.ts"
15 | ],
16 | "amdName": "GlobalHookState",
17 | "sideEffects": false,
18 | "scripts": {
19 | "format": "prettier --write '**/*.ts' '**/*.tsx'",
20 | "example": "parcel example/index.html",
21 | "build": "rm -rf dist && microbundle --external react --globals react=React --strict --no-compress"
22 | },
23 | "keywords": [],
24 | "author": {
25 | "name": "Minh Phan",
26 | "email": "phanminh65@gmail.com"
27 | },
28 | "repository": {
29 | "url": "git@github.com:paul-phan/global-state-hook.git",
30 | "type": "git"
31 | },
32 | "license": "MIT",
33 | "devDependencies": {
34 | "@types/react": "^18.0.28",
35 | "@types/react-dom": "^18.0.11",
36 | "microbundle": "^0.15.1",
37 | "parcel": "^2.8.3",
38 | "prettier": "^2.8.4",
39 | "process": "^0.11.10",
40 | "react": "^18.2.0",
41 | "react-dom": "^18.2.0",
42 | "ts-node": "^10.9.1",
43 | "typescript": "^4.9.5"
44 | },
45 | "dependencies": {}
46 | }
47 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | type Listener = (newState: S) => void
4 |
5 | export interface ISubscription {
6 | __updateState?: (nextState: any, forceUpdate?: boolean) => void
7 | subscribe: (fn: Listener) => () => void
8 | unsubscribe: (fn: Listener) => void
9 | listener: Set>
10 | state: S
11 | updateState: (nextState: S | any, forceUpdate?: boolean) => void
12 | [key: string]: any
13 | }
14 | export interface IReactive {
15 | subscribe: (fn: Listener) => void
16 | unsubscribe: (fn: Listener) => void
17 | listener: Set>
18 | store: S
19 | }
20 |
21 | export function createSubscription(
22 | initialState?: S,
23 | ): ISubscription {
24 | let state: S = initialState || ({} as any)
25 | let listener: Set> = new Set()
26 |
27 | const unsubscribe = (fn: Listener) => listener.delete(fn)
28 | const subscribe = (fn: Listener) => {
29 | listener.add(fn)
30 | return () => unsubscribe(fn)
31 | }
32 | const updateState = (nextState: S, forceUpdate = true) => {
33 | state = { ...state, ...nextState }
34 | if (forceUpdate) {
35 | listener.forEach((fn) => fn(nextState))
36 | }
37 | }
38 | let store = {
39 | subscribe,
40 | unsubscribe,
41 | listener,
42 | get state() {
43 | return state
44 | },
45 | set state(nextState) {
46 | state = nextState
47 | },
48 | updateState,
49 | useSubscriptionValue: (pick?: Array) => {
50 | return useSubscription(store, pick)
51 | },
52 | useDispatch: (reducer: () => any) => {
53 | return useReducerSubscription(store, reducer)
54 | },
55 | }
56 | return store
57 | }
58 |
59 | export interface IStateUpdater {
60 | changed?: any
61 | setState: (newState: any, callback?: (newState: S) => void) => void
62 | state: S
63 | }
64 |
65 | export interface IStateReduceUpdater {
66 | dispatch: (p: any) => void
67 | state: S
68 | }
69 |
70 | function useSubscriber>(
71 | subscriber: ISubscription,
72 | pick?: P,
73 | ) {
74 | if (!subscriber) {
75 | console.trace("Missing subscriber!!")
76 | }
77 | const [changed, setUpdate] = React.useState({})
78 | const updater = React.useCallback(
79 | (nextState: S) => {
80 | if (
81 | subscriber &&
82 | (!pick?.length ||
83 | Object.keys(nextState).find((k) => pick.some((item) => item === k)))
84 | ) {
85 | setUpdate({})
86 | }
87 | },
88 | [pick],
89 | )
90 | React.useEffect(() => {
91 | if (subscriber) {
92 | subscriber.subscribe(updater)
93 | return () => subscriber.unsubscribe(updater)
94 | }
95 | return
96 | // eslint-disable-next-line react-hooks/exhaustive-deps
97 | }, [])
98 | return changed
99 | }
100 |
101 | export function useReducerSubscription(
102 | subscriber: ISubscription,
103 | reducer: any = () => {},
104 | ): IStateReduceUpdater {
105 | useSubscriber(subscriber)
106 | const dispatch = (...args: any) => {
107 | const newState = reducer(subscriber.state, ...args)
108 | subscriber.state = Object.assign({}, subscriber.state, newState)
109 | subscriber.listener.forEach((fn) => fn(newState))
110 | }
111 | React.useDebugValue(subscriber.state)
112 |
113 | return { state: subscriber?.state, dispatch }
114 | }
115 |
116 | export function useSubscription<
117 | S extends object,
118 | P extends [keyof S] | Array,
119 | >(subscriber: ISubscription, pick?: P): IStateUpdater {
120 | const changed = useSubscriber(subscriber, pick)
121 | React.useDebugValue(subscriber?.state)
122 |
123 | return {
124 | changed,
125 | state: subscriber?.state,
126 | setState: React.useCallback(
127 | (newState: any, callback?: Function) => {
128 | if (!subscriber) {
129 | return
130 | }
131 | if (typeof newState === "object" && newState.constructor === Object) {
132 | subscriber.state = Object.assign({}, subscriber.state, newState)
133 | } else if (typeof newState === "function") {
134 | const nextState = newState(subscriber.state)
135 | subscriber.state = Object.assign({}, subscriber.state, nextState)
136 | } else {
137 | subscriber.state = newState
138 | }
139 | subscriber.listener.forEach((fn) => fn(newState))
140 | callback && callback()
141 | },
142 | // eslint-disable-next-line react-hooks/exhaustive-deps
143 | [subscriber.state, pick],
144 | ),
145 | }
146 | }
147 |
148 | export const createReactive = (
149 | initialState: S,
150 | ): IReactive => {
151 | let listener: Set> = new Set()
152 | const subscribe = (fn: Listener) => listener.add(fn)
153 | const unsubscribe = (fn: Listener) => listener.delete(fn)
154 | const store: S = new Proxy(initialState, {
155 | set(target, p: string, value, receiver) {
156 | listener.forEach((fn) => fn(p))
157 | return Reflect.set(target, p, value, receiver)
158 | },
159 | })
160 | return {
161 | listener,
162 | store,
163 | subscribe,
164 | unsubscribe,
165 | }
166 | }
167 |
168 | export const useReactive = (
169 | reactiveStore: IReactive,
170 | pick?: Array,
171 | ) => {
172 | const [, setUpdate] = React.useState({})
173 | const updater = React.useCallback(
174 | (prop: string) => {
175 | if (!pick || (Array.isArray(pick) && pick.includes(prop))) {
176 | setUpdate({})
177 | }
178 | },
179 | [pick],
180 | )
181 | React.useEffect(() => {
182 | reactiveStore.subscribe(updater)
183 | return () => reactiveStore.unsubscribe(updater)
184 | // eslint-disable-next-line react-hooks/exhaustive-deps
185 | }, [])
186 | return reactiveStore.store
187 | }
188 |
189 | export const useSyncStore = (
190 | subscription: ISubscription,
191 | getServerSnapshot?: () => S,
192 | ) => {
193 | let state = React.useSyncExternalStore(
194 | subscription.subscribe,
195 | () => subscription.state,
196 | getServerSnapshot,
197 | )
198 | return {
199 | state,
200 | subscription,
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src"],
3 | "exclude": ["test", "example"],
4 | "compilerOptions": {
5 | "target": "ES5",
6 | "module": "ESNext",
7 | "jsx": "react",
8 | "declaration": true,
9 | "declarationMap": true,
10 | "strict": true,
11 | "noUnusedLocals": true,
12 | "noUnusedParameters": true,
13 | "noImplicitReturns": true,
14 | "noFallthroughCasesInSwitch": true,
15 | "esModuleInterop": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------