Skip to content

Instantly share code, notes, and snippets.

@alexeyraspopov
Created June 27, 2022 21:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexeyraspopov/1a5677a77031395a9c48bc68ecbbf6c9 to your computer and use it in GitHub Desktop.
Save alexeyraspopov/1a5677a77031395a9c48bc68ecbbf6c9 to your computer and use it in GitHub Desktop.

This implementation makes use of useSyncExternalStore() that is available in React 18 or via shim.

Example

Store declaration:

// CounterStore.js
import { createStore } from 'thelib'

let [CounterProvider, useCounter] = createStore((set) => ({
  counter: 0,
  increment() {
    set(state => ({ counter: state.counter + 1 }));
  },
}))

export { CounterProvider, useCounter }

Store consumption:

// CounterView.jsx
import { CounterProvider, useCounter } from './CounterStore'

function View() {
  return (
    <CounterProvider>
      <Output />
      <Controls />
    </CounterProvider>
  )
}

function Output() {
  // only gets updated with selector has changed
  let value = useCounter(state => state.counter)
  return <span>{value}</span>
}

function Controls() {
  // never gets re-rendered
  let increment = useCounter(state => state.increment)
  return <button onClick={increment}>+</button>
}
import * as React from 'react'
import { createContext, useContext, useMemo } from 'react'
import { useSyncExternalStore } from 'use-sync-external-store/shim'
export function createStore(fn) {
let Ctx = createContext()
function Provider({ children }) {
let source = useMemo(() => createSource(fn), [])
return <Ctx.Provider value={source} children={children} />
}
let useSelector = (selector) => {
let source = useContext(Ctx)
let getSnapshot = () => selector(source.getState())
return useSyncExternalStore(source.subscribe, getSnapshot)
};
return [Provider, useSelector];
}
function createSource(fn) {
let events = new EventTarget()
let state = fn(set)
function set(fn) {
state = Object.assign(state, fn(set))
events.dispatchEvent(new CustomEvent('update'))
}
function subscribe(cb) {
events.addEventListener('update', cb)
return () => events.removeEventListener('update', cb)
}
return { getState() { return state }, subscribe }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment