I’m having some trouble to wrap my head around how to create a generic context.
Basically what i want is to be able to pass a value to the Root, and then have the children infer the correct type, is this even possible? I know that it’s more than likely because there’s no connection between TabHeaderProps, TabContextProps and TabRootProps.
This is a simplified version that works, but it’s more verbose than I want it to be.
import React, { Dispatch, SetStateAction, useState } from 'react';
type Headers = 'Foo' | 'Bar' | 'Baz';
const defaultValue: Headers = 'Foo';
type TabsContextProps<T = any> = {
activeTab: T;
setActiveTab: Dispatch<SetStateAction<T | undefined>>;
};
const TabsContext = React.createContext<TabsContextProps | undefined>(
undefined,
);
type TabsRootProps<T> = {
defaultValue: T;
children: React.ReactNode;
};
export function TabsRoot<T>({ children }: TabsRootProps<T>) {
const [activeTab, setActiveTab] = useState<T>();
return (
<TabsContext.Provider
value={{
setActiveTab,
activeTab,
}}
>
{children}
</TabsContext.Provider>
);
}
function useTabsRootContext<T>(): TabsContextProps<T> {
if (!React.useContext(TabsContext)) {
throw new Error('useTabsRootContext must be used inside TabsRoot');
}
return React.useContext(TabsContext) as TabsContextProps<T>;
}
type TabHeaderProps<T> = {
value: T;
};
const TabsHeader = <T,>({ value }: TabHeaderProps<T>) => {
const { activeTab } = useTabsRootContext<T>();
if (!activeTab !== value) {
return null;
}
return <p>Im Active</p>;
};
const Tabs = () => {
<TabsRoot<Headers> defaultValue={defaultValue}>
<TabsHeader<Headers> value="Baz" /> {/* This work but i preferably want this to be infered based on the TabsRootType */}
</TabsRoot>;
};
Ideally i would want to have it:
const Tabs = () => {
<TabsRoot defaultValue={defaultValue}>
<TabsHeader value="Baz" />
<TabsHeader value="error" /> {/* I want this to throw an error */}
</TabsRoot>;
};
>Solution :
Unfortunately this is indeed impossible:
- React component instances have no clue about their parent components, other than through their props and context. In your case you make no explicit connection through props, only with context
- but TypeScript generic type parameter inference occurs only with arguments of function call or of class constructor (works for React component instance props as well). Hence React context has no way to affect type inference