Skip to content

Instantly share code, notes, and snippets.

@trevor-atlas
Last active October 26, 2022 06:04
Show Gist options
  • Save trevor-atlas/32df508f6c852375a08ee9a0d2830993 to your computer and use it in GitHub Desktop.
Save trevor-atlas/32df508f6c852375a08ee9a0d2830993 to your computer and use it in GitHub Desktop.
contextless data store with react 18
import { useSyncExternalStore } from 'react';
export function createStore<Store>(
creator: (
set: (setter: (store: Store) => Store) => void,
get: () => Store
) => Store
) {
let store = {} as Store;
const get = () => store;
const subscribers = new Set<() => void>();
const set = (next: (snapshot: Store) => Store) => {
if (typeof next !== 'function') {
throw new Error('Store updater must be a function');
}
store = next(store);
subscribers.forEach((cb) => cb());
};
const subscribe = (callback: () => void) => {
subscribers.add(callback);
return () => subscribers.delete(callback);
};
let initialState = creator(set, get);
store = { ...initialState };
const useStoreData = (): {
get: () => Store;
set: (next: (snapshot: Store) => Store) => void;
subscribe: (callback: () => void) => () => void;
} => ({
get,
set,
subscribe
});
function useStore<SelectorOutput>(
selector: (store: Store) => SelectorOutput
): SelectorOutput {
const store = useStoreData();
if (!store) {
throw new Error('Store not found');
}
const state = useSyncExternalStore(
store.subscribe,
() => selector(store.get()),
() => selector(initialState)
);
return state;
}
return useStore;
}
@trevor-atlas
Copy link
Author

trevor-atlas commented Oct 24, 2022

use like so:

import { createStore } from "./createStore";

// do this anywhere! doesn't have to be alongside components
const useUser = createStore<{
  name: string;
  email: string;
  setEmail: (email: string) => void;
  setName: (name: string) => void;
}>((set, get) => ({
  email: "[email protected]",
  name: "Finn",
  setEmail: (email: string) => {
    set((s) => ({ ...s, email }));
  },
  setName: (name: string) => {
    set((s) => ({ ...s, name }));
  }
}));

function NameInput() {
  const name = useUser((store) => store.name);
  const setName = useUser((store) => store.setName);
  console.log("name render");
  return (
    <>
      <h1>Hello {name}</h1>
      <input
        type="text"
        name="name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
    </>
  );
}

function EmailInput() {
  const email = useUser((store) => store.email);
  const setEmail = useUser((store) => store.setEmail);
  console.log("email render");
  return (
    <>
      <h1>Your email is: {email}</h1>
      <input
        type="text"
        name="name"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
    </>
  );
}

export default function App() {
  console.log("App render");
  return (
    <div className="App">
      <NameInput />
      <EmailInput />

      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

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