English
(Please contribute translations!)
Use Global State Hook
Super tiny state sharing library with hooks
Install
npm install --save global-state-hook
Update 2023:
With React 18 support useSyncExternalStore, you can now write your React App like that:
let textStore3 = createSubscription({ value: "Text 3", value2: "Text 4" })
function Text3() {
let { state, subscription } = useSyncStore(textStore3)
return (
<div>
<input
value={state.value}
onChange={(e) => subscription.updateState({ value: e.target.value })}
/>
</div>
)
}
function Text4() {
let { state, subscription } = useSyncStore(textStore3)
return (
<div>
<input
value={state.value2}
onChange={(e) => subscription.updateState({ value2: e.target.value })}
/>
</div>
)
}
Update 2022:
With new Reactive pattern, you can now write your React App like that:
const sourceOfTruth = createReactive({
text1: "Text 1 sync together",
text2: "Text 2 walk alone.",
})
const Text1 = () => {
const state = useReactive(sourceOfTruth, ["text1"])
return (
<input
value={state.text1}
onChange={(e) => (state.text1 = e.target.value)}
/>
)
}
const Text2 = () => {
const state = useReactive(sourceOfTruth, ["text2"])
return (
<input
value={state.text2}
onChange={(e) => (state.text2 = e.target.value)}
/>
)
}
const ReactiveApp = () => {
return (
<div>
<h1>Reactive pattern:</h1>
<Text1 />
<Text2 />
<Text1 />
</div>
)
}
Example
import React from "react"
import { createSubscription, useSubscription } from "global-state-hook"
import { render } from "react-dom"
const counterSubscription = createSubscription({ count: 0, foo: 10 })
function CounterDisplay() {
let { state, setState } = useSubscription(counterSubscription)
return (
<div>
<button onClick={() => setState({ count: state.count - 1 })}>-</button>
<span>{state.count}</span>
<button onClick={() => setState({ count: state.count + 1 })}>+</button>
</div>
)
}
function FooDisplay() {
// Only update when foo change
let { state, setState } = useSubscription(counterSubscription, ["foo"])
return (
<div>
<button onClick={() => setState({ foo: state.foo - 1 })}>-</button>
<span>{state.foo}</span>
<button onClick={() => setState({ foo: state.foo + 1 })}>+</button>
</div>
)
}
function App() {
return (
<>
<CounterDisplay />
<FooDisplay />
</>
)
}
render(<App />, document.getElementById("root"))
API
createSubscription(initialState)
import { createSubscription } from "global-state-hook"
const counterSubscription = createSubscription({ count: 0 })
useSubscription(subscriber)
import { createSubscription, useSubscription } from "global-state-hook"
const counterSubscription = createSubscription({ count: 0 })
// Your custom hook goes here so you can share it to anywhere
const useCounter = () => {
let { state, setState } = useSubscription(counterSubscription)
const increment = () => setState({ count: state.count + 1 })
const decrement = () => setState({ count: state.count + 1 })
return { count: state.count, increment, decrement }
}
function Counter() {
let { count, increment, decrement } = useCounter()
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
)
}
useSubscription(subscriber, pick: string[])
import { createSubscription, useSubscription } from "global-state-hook"
const counterSubscription = createSubscription({ count: 0, foo: 10 })
function Foo() {
// Only update when foo change
let { state, setState } = useSubscription(counterSubscription, ["foo"])
return (
<div>
<button onClick={() => setState({ foo: state.foo - 1 })}>-</button>
<span>{state.foo}</span>
<button onClick={() => setState({ foo: state.foo + 1 })}>+</button>
</div>
)
}
Why use global-state-hook?
It's minimal You only need to learn 2 API: createSubscription, useSubscription.
It's easy to integrate Can integrate with any React library.
It's small Only 50 lines of code for this library.
Guide
Global state is not bad.
It's about how you manage it, global state will not be bad if you do it the right way.
You can consider using Context.Provider
to provide your global subscription so it can be clean up after the component unmounted. Example:
const TextContext = React.createContext < any > null
const useTextValue = () => {
const textSubscription = useContext(TextContext)
let { state, setState } = useSubscription(textSubscription)
const onChange = (e) => setState({ value: e.target.value })
return { value: state.value, onChange }
}
function Text() {
let { value, onChange } = useTextValue()
return (
<div>
<input value={value} onChange={onChange} />
</div>
)
}
function TextComponent() {
const textSubscription = createSubscription({
value: "The text will sync together",
})
return (
<TextContext.Provider value={textSubscription}>
<Text />
</TextContext.Provider>
)
}
Tip#1: Select your state property that you want to subscribe to
By default some people might recommend you to put only one state to a subscription
, for example:
const textSubscription = createSubscription("Your initial state here")
let { state: text, setState: setText } = useSubscription(textSubscription)
// Change the text value:
setText("New text value")
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:
import { createSubscription, useSubscription } from "global-state-hook"
const multiStateSubscription = createSubscription({ foo: 1, bar: 2, baz: 3 })
function Foo() {
// Only update when foo or baz change
let { state, setState } = useSubscription(multiStateSubscription, [
"foo",
"baz",
])
return (
<div>
<button onClick={() => setState({ foo: state.foo - 1 })}>-</button>
<span>{state.foo}</span>
<span>{state.baz}</span>
<button onClick={() => setState({ foo: state.foo + 1 })}>+</button>
</div>
)
}
Reducer pattern:
For those who still in love with redux, the reducer pattern will fit for you:
const counterSubscription = createSubscription({ count: 0 })
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 }
case "decrement":
return { count: state.count - 1 }
default:
throw new Error()
}
}
function Counter() {
const { state, dispatch } = useReducerSubscription(
counterSubscription,
reducer,
)
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
)
}
Misc
Support debugging with React Developer Tools
It's so easy right? :D