Skip to content

Instantly share code, notes, and snippets.

@twfarland
Created May 13, 2020 22:45
Show Gist options
  • Save twfarland/36230c0e5b007a69ff0203617d4c7754 to your computer and use it in GitHub Desktop.
Save twfarland/36230c0e5b007a69ff0203617d4c7754 to your computer and use it in GitHub Desktop.
createStore hooks to share state across components, a lightweight and simpler replacement for redux or even context
import { useState, useEffect, Dispatch, SetStateAction } from "react"
type SetState<S> = Dispatch<SetStateAction<S>>
/*
Shares state amongst all instances, without using context API
This is a more lightweight redux alternative
Note that the initial state is set outside of any mounted instance,
as in redux.
*/
export type UseStore<S> = readonly [S, (nextState: S) => void]
export interface Persister<S> {
write(state: S): void
read(): S
}
export function memoryPersister<S>(initialState: S): Persister<S> {
let state: S = initialState
const write = (nextState: S) => {
state = nextState
}
const read = () => state
return { write, read }
}
export function localPersister<S>(initialState: S, key: string): Persister<S> {
let state: S
const stored = localStorage.getItem(key)
state = stored ? (JSON.parse(stored) as S) : initialState
const write = (nextState: S) => {
if (nextState === null || nextState === undefined) {
localStorage.removeItem(key)
} else {
localStorage.setItem(key, JSON.stringify(nextState))
}
state = nextState
}
const read = () => state
return { write, read }
}
export function createStore<S>(persister: Persister<S>) {
let listeners: SetState<S>[] = []
function setState(nextState: S) {
persister.write(nextState)
// notify others of change
listeners.forEach((setStateInstance) => {
setStateInstance(nextState)
})
}
return function useStore(): UseStore<S> {
const state = persister.read()
const setStateInstance = useState<S>(state)[1]
useEffect(() => {
listeners.push(setStateInstance) // listen for changes on other instances
return () => {
// remove listener on unmount
listeners = listeners.filter(
(listener) => listener !== setStateInstance
)
}
}, [setStateInstance]) // only run once
// use the shared state/setState, not the instance
return [state, setState] as const
}
}
export function createMemoryStore<S>(initialState: S) {
return createStore(memoryPersister(initialState))
}
export function createLocalStore<S>(initialState: S, key: string) {
return createStore(localPersister(initialState, key))
}
@twfarland
Copy link
Author

Example usage:

const useCount = createMemoryStore(0) // or createLocalStore(0, 'count')

const Child: React.FC<{ id: string }> = ({ id }) => {
    const [count, setCount] = useCount()
    return (
        <div id={id}>
            <b>{count}</b>
            <button onClick={() => setCount(count + 1)}>+</button>
        </div>
    )
}

const Parent: React.FC = () => {
    return (
        <div>
            <Child id="a" />
            <Child id="b" />
        </div>
    )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment