Skip to content

Instantly share code, notes, and snippets.

@ThomasHoadley
Created February 13, 2023 11:25
Show Gist options
  • Save ThomasHoadley/3b097f8223bb42c2fe8c93282b9d65b9 to your computer and use it in GitHub Desktop.
Save ThomasHoadley/3b097f8223bb42c2fe8c93282b9d65b9 to your computer and use it in GitHub Desktop.
Fast Context
import React, {
useRef,
createContext,
useContext,
useCallback,
useSyncExternalStore,
} from "react";
/**
* A performant context which is useful for high velocity state changes e.g. typing in an input field.
* See https://www.youtube.com/watch?v=ZKlXqrcBx88&list=PLQ8S2mDORPQmFOihpTcoH7jUGkszUVeym&index=23&t=783s for more info.
* Implementation: wrap component with Provider, then useStore() e.g.
* const [value, setStore] = useStore((store) => store[value]);
* @param initialState Any store state. Use the generic to define the type.
* @returns { Provider, useStore }
*/
export default function createFastContext<Store>(initialState: Store) {
function useStoreData(): {
get: () => Store;
set: (value: Partial<Store>) => void;
subscribe: (callback: () => void) => () => void;
} {
const store = useRef(initialState);
const get = useCallback(() => store.current, []);
const subscribers = useRef(new Set<() => void>());
const set = useCallback((value: Partial<Store>) => {
store.current = { ...store.current, ...value };
subscribers.current.forEach((callback) => callback());
}, []);
const subscribe = useCallback((callback: () => void) => {
subscribers.current.add(callback);
return () => subscribers.current.delete(callback);
}, []);
return {
get,
set,
subscribe,
};
}
type UseStoreDataReturnType = ReturnType<typeof useStoreData>;
const StoreContext = createContext<UseStoreDataReturnType | null>(null);
function Provider({ children }: { children: React.ReactNode }) {
return (
<StoreContext.Provider value={useStoreData()}>
{children}
</StoreContext.Provider>
);
}
function useStore<SelectorOutput>(
selector: (store: Store) => SelectorOutput
): [SelectorOutput, (value: Partial<Store>) => void] {
const store = useContext(StoreContext);
if (!store) {
throw new Error("Store not found");
}
const state = useSyncExternalStore(
store.subscribe,
() => selector(store.get()),
() => selector(initialState),
);
return [state, store.set];
}
return {
Provider,
useStore,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment