import * as React from 'react';
/**
* Context Provider와 useContext 훅 쌍을 생성하는 팩토리 함수입니다.
*
* Provider 없이 훅을 사용하면 런타임 에러를 던져 잘못된 사용을 조기에 감지할 수 있다.
*
* @reference https://github.com/toss/react-simplikit/blob/main/packages/core/src/utils/buildContext/buildContext.tsx
* @param rootComponentName - Provider의 displayName 및 에러 메시지에 사용될 루트 컴포넌트 이름
* @param defaultContext - Context 내 기본값
* @returns `[Provider, useContext]` 튜플
*
* @example
* const [FooProvider, useFoo] = createSafeContext<FooContextValue>('Foo');
*
* function FooProvider({ value, children }) {
* return <FooProvider {...value}>{children}</FooProvider>;
* }
*
* function ChildComponent() {
* const foo = useFoo(); // Provider 밖에서 호출하면 에러
* }
*/
export function createSafeContext<ContextValueType extends object | null>(
rootComponentName: string,
defaultContext?: ContextValueType,
) {
const Context = React.createContext<ContextValueType | undefined>(defaultContext);
function Provider(props: React.PropsWithChildren<ContextValueType>) {
const { children, ...context } = props;
// context 객체의 참조 안정성을 위해 values 기반으로 메모이제이션
const value = React.useMemo(() => context, Object.values(context)) as ContextValueType;
return <Context.Provider value={value}>{children}</Context.Provider>;
}
function useContext(consumerName?: string) {
const context = React.useContext(Context);
if (context) return context;
// defaultContext가 명시적으로 제공된 경우 fallback으로 반환
if (defaultContext !== undefined) return defaultContext;
throw new Error(
`\`${consumerName || rootComponentName}\`은 반드시 ${rootComponentName}Provider 안에서 사용해야 합니다`,
);
}
// React DevTools에서 컴포넌트를 식별할 수 있도록 displayName 설정
Provider.displayName = rootComponentName + 'Provider';
return [Provider, useContext] as const;
}
createSafeContext
React Context API를 안전하게 생성해주는 팩토리 함수입니다
3