skip to Main Content

I’m having a bit of trouble figuring out how to handle generic types when receiving component properties in Typescript and React.
For example, here I am trying to make sure that the type of options in the columns property of the SubComponent component is inherited from the generic type OptionsAB of the object passed to optionSet: PropertiesAB.

Here the code I’m testing

interface OptionSet<T> {
  options: T[];
}

interface SubComponentProps<TFilter = unknown> {
  columns: {
    optionSet: OptionSet<TFilter>;
    options: TFilter;
  }[];
}

export function SubComponent<TFilter = unknown>(_: SubComponentProps<TFilter>) {
  return null;
}

interface OptionsAB {
  a: string;
  b: string;
}
interface OptionsCS {
  c: string;
  s: string;
}
const PropertiesAB: OptionSet<OptionsAB> = { options: [] };
const PropertiesCS: OptionSet<OptionsCS> = { options: [] };

export const MainComponent = () => {
  return (
    <SubComponent
      columns={[
        { optionSet: PropertiesAB, options: { a: "b", b: "s" } }, // This should work
        { optionSet: PropertiesAB, options: { a: "b" } }, // This should throw error
        { optionSet: PropertiesCS, options: { a: "b" } }, // This should throw error
      ]}
    />
  );
};

But the type of options is always unknown. And the type of optionSet is also read as OptionSet<unknown> and not OptionSet<OptionsAB>

Where am I going wrong?
Thanks

2

Answers


  1. The main cause of your problem, is that typescript generic will infer type from the most "shallow" generic.. in your case:

    interface SubComponentProps<TFilter = unknown> {
      columns: {
        optionSet: OptionSet<TFilter>;
        options: TFilter; <<here
      }[];
    }
    

    in new typescript there will be new NoInfer helper type and it should work…

    Playground

    currently you can use some custom helper type like:

    export type NoInfer<A extends any> =
        [A][A extends any ? 0 : never]
    

    Playground

    Login or Signup to reply.
  2. Create a NoInfer helper type in TypeScript. This type utilizes conditional types to prevent TypeScript from inferring the type too early. Here’s how you can integrate NoInfer into your code:

    // Define a NoInfer helper type
    type NoInfer<T> = T & { [K in keyof T]: T[K] };
    
    // Define a generic interface for option sets
    interface OptionSet<T> {
      options: T[];
    }
    
    // Define the props for the sub component
    interface SubComponentProps<TFilter = unknown> {
      columns: {
        optionSet: OptionSet<TFilter>;
        options: NoInfer<TFilter>; // Use NoInfer here
      }[];
    }
    
    // Define the SubComponent function
    export function SubComponent<TFilter = unknown>(props: SubComponentProps<TFilter>) {
      return null; // Just a placeholder
    }
    
    // Define interfaces for different sets of options
    interface OptionsAB {
      a: string;
      b: string;
    }
    interface OptionsCS {
      c: string;
      s: string;
    }
    
    // Create option sets for different types of options
    const PropertiesAB: OptionSet<OptionsAB> = { options: [] };
    const PropertiesCS: OptionSet<OptionsCS> = { options: [] };
    
    // Main component using SubComponent
    export const MainComponent = () => {
      return (
        <SubComponent
          columns={[
            { optionSet: PropertiesAB, options: { a: "b", b: "s" } }, // This should work
            // { optionSet: PropertiesAB, options: { a: "b" } }, // This should throw error
            // { optionSet: PropertiesCS, options: { a: "b" } }, // This should throw error
          ]}
        />
      );
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search