├── .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 | [![Edit bold-ellis-6rg1t](https://codesandbox.io/static/img/play-codesandbox.svg)](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 | 103 | {state.count} 104 | 105 |
106 | ) 107 | } 108 | function FooDisplay() { 109 | // Only update when foo change 110 | let { state, setState } = useSubscription(counterSubscription, ["foo"]) 111 | return ( 112 |
113 | 114 | {state.foo} 115 | 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 | 183 | {state.foo} 184 | 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 | 266 | {state.foo} 267 | {state.baz} 268 | 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 | 300 | 301 | 302 | ) 303 | } 304 | ``` 305 | 306 | ### Misc 307 | 308 | # Support debugging with React Developer Tools 309 | 310 | ![React Dev Tools](./example/react-dev-tools.png) 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 | 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 | 55 | {state.foo} 56 | 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 | 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 | 165 | 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 | --------------------------------------------------------------------------------