Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Creating a generic Context that allows for the children within to be inferred correctly

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading