1.1.1 • Published 5 months ago

singleton-state-hook v1.1.1

Weekly downloads
-
License
ISC
Repository
github
Last release
5 months ago

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 React useState 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.

1.1.1

5 months ago

1.1.0

5 months ago

1.0.8

5 months ago

1.0.7

5 months ago

1.0.6

5 months ago

1.0.5

5 months ago

1.0.4

5 months ago

1.0.3

5 months ago

1.0.2

5 months ago

1.0.1

5 months ago

1.0.0

5 months ago