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>;
};
Unfortunately this is indeed impossible: