Skip to content

Instantly share code, notes, and snippets.

@rjcnd105
Created February 22, 2024 04:14
Show Gist options
  • Select an option

  • Save rjcnd105/7d6ec872583f3691030066c89eb700e3 to your computer and use it in GitHub Desktop.

Select an option

Save rjcnd105/7d6ec872583f3691030066c89eb700e3 to your computer and use it in GitHub Desktop.
import React, { createContext, useCallback, useContext, useMemo, useRef, useSyncExternalStore } from "react";
type MakeStoreProps<T> = {
name: string
initial: T
}
type Subscriber = () => void
type SubscriberStore<T> = {
get() : T
set(setter: (prev: T) => T) : void
subscribe(callback: Subscriber): () => void
}
type SetRegister<T, > = (setter: (value: T) => T) => void
export const makeContextSyncExternalStore = <V,>(param: MakeStoreProps<V>) => {
const initValue = param.initial
const StoreContext = createContext<SubscriberStore<V> | null>(null)
const Provider = <P,>({children, ...props}: React.PropsWithChildren<P>) => {
const storeValue = useRef<V>(initValue);
const subscribers = useRef(new Set<Subscriber>());
const store: SubscriberStore<V> = useMemo(
() => ({
get: () => storeValue.current,
set: (setFn) => {
storeValue.current = setFn(storeValue.current);
subscribers.current.forEach((callback) => callback());
},
subscribe: (callback) => {
subscribers.current.add(callback);
return () => {
subscribers.current.delete(callback);
};
},
}),
[]
);
return (
<StoreContext.Provider value={store}>
{children}
</StoreContext.Provider>
)
}
StoreContext.displayName = `${param.name}Context`;
Provider.displayName = `${param.name}Provider`;
const useStore = <R, >(selector: (v: V) => R): [getter: R, setRegister: SetRegister<V>] => {
const store = useContext(StoreContext);
if (!store) {
throw Error(`${StoreContext.displayName} Context를 찾을 수 없습니다.`);
}
const state = useSyncExternalStore(store.subscribe, () => selector(store.get()), () => selector(store.get()));
return [state, store.set] as const;
}
return [
Provider,
useStore,
] as const
}
const [Provider1, useStore1] = makeContextSyncExternalStore({
name: "test",
initial: 0
})
const [Provider2, useStore2] = makeContextSyncExternalStore<number | null>({
name: "test",
initial: null
})
export default makeContextSyncExternalStore
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment