singleton-state-hook v1.1.1
Singleton State
A 1kB alternative to React Context with improved performance and ergonomics.
npm install singleton-state-hook
Interactive demo: https://singletonstate.web.app/
Define your state
Every piece of state is a hook. Create state using singletonState
.
import { singletonState } from 'singleton-state-hook'
export const useCount = singletonState(0)
Use your state
Just import and use the hook in your components, no providers needed.
function Display() {
const count = useCount.value()
return <h1>{count}</h1>
}
function Increment() {
const count = useCount.value()
return <button onClick={() => useCount.set(count + 1)}>Increment</button>
}
Performance benefits?
React Context causes all consumers of the context to re-render every time the value changes. Commonly there will be multiple values in the same context like so:
export const Context = createContext<{
count: number
setCount: Dispatch<SetStateAction<number>>
someOtherState: string
}>(null!)
This will mean that any subscribers of someOtherState
will get a re-render every time count
changes, unless we wrap our app with a new provider for every piece of state.
With Singleton State, each piece of state will be separated into its own hook, so there will be no unnecessary re-renders.
How does it work under the hood?
The entire library is only about 30 lines long:
export function singletonState<T>(initialState: T) {
// keep a set of subscribers in this scope, each piece of state will have a separate set
const subscribers = new Set<(value: React.SetStateAction<T>) => void>()
let localValue = initialState
return {
// return the hook
value: () => {
const [value, setValue] = useState(localValue)
// on mount add the subscriber to the set
useEffect(() => {
subscribers.add(setValue)
return () => {
subscribers.delete(setValue)
}
}, [])
return value
},
// return a setter without the hook
set: (newValue: SetStateAction<T>) => {
if (typeof newValue === 'function') {
localValue = (newValue as (value: T) => T)(localValue)
} else {
localValue = newValue
}
subscribers.forEach(subscriber => subscriber(localValue))
},
}
}
- each instance of
singletonState
stores a list of subscribers (i.e. components that consume the hook) - each subscriber will be given their own
value
to react to using the standard ReactuseState
hook - whenever a subscriber uses
set
, all subscribers are kept in sync through the subscription list
In effect, each consumer of the state has their own useState
created by the hook. Then, outside the standard React lifecycle, all states are being synced whenever the values changes.