Determine the field's type without using generic type arguments

In my code, there is a type called Component with a generic parameter named Props, which must adhere to the Record<string, any> structure. I am looking to create a type that can accept a component in one property and also include a function that returns the Props for that component. These objects will then be stored in a dictionary format like so:

const components: Record<string, ComponentConfig> = {
  someKey: {
    component: Component1, // Implements the Component<{num: number}> type,
    props() { // Strongly typed Props
      return {
        num: 1
      }
    }
  },
  someOtherKey: {
    component: Component2, // Implements the Component<{str: string}> type,
    props() { // Strongly typed Props
      return {
        str: "test"
      }
    }
  }
}

I attempted a solution using this approach:

type ComponentConfig = never extends infer Props
  ? Props extends Record<string, any>
    ? {
        component: Component<Props>
        props(): Props
      }
    : string // originally never, modified for this example
  : never

However, I encountered an error when assigning an object to a key within the dictionary:

// TS2322: Type { component: Component<{ num: number; }>; props(): { num: number; }; } is not assignable to type  never 
const cfg: Record<string, ComponentConfig> = {
    someKey: {
        component: component1,
        props() {
            return {
                num: 1
            }
        }
    }
}

I am puzzled by this error as 'never' seems to extend everything. The logic dictates that the first ternary expression should always evaluate to true because the empty set is a subset of every set. Thus, depending on the type of Props, the resolved type should either be

{component: Component<Props>; props(): Props}
or string. However, I consistently receive 'never', which confuses me.

I am using these methods to introduce a new type variable Props. If there are more elegant solutions to achieve this, I am open to suggestions.

Initially, I thought about passing it as a generic argument, but that would not work since I would need to specify it while creating the cfg Record. Using any/unknown as a placeholder would lead to incorrect typings for the props function. Essentially, my goal is to infer the generic argument of the component property and utilize it as the return type for the props function.

Answer №1

This topic can be a bit confusing when it comes to distributive conditional types. Essentially, what you need to do is enclose your condition in [] to make it actually true.

type ComponentConfig = never extends infer Props
  ? [Props] extends [Record<string, any>]
    ? {
        component: Component<Props>
        props(): Props
      }
    : never
  : never

If we analyze it further, never in a Union Type acts as if it doesn't exist at all.

type Result = string | number | never; // string | number
type Result2 = string | never; // string
type Result3 = never; // now it is never

When conditions result in never (empty union), it always returns another empty union which is also never. By enclosing never in array brackets ([]), we can retain that value in our union.

type Result4 = string | [never]; // string | [never];

However, we encounter difficulties with the typing of the component because the generic argument T of Component<T> ends up as never. To resolve this, pass the Props as a generic to your ComponenConfig<Props>. The use of infer here seems unnecessary.

type Component<Props extends Record<string, any>> = {
  props: Props;
};

type ComponentConfig<Props> = never extends Props
  ? [Props] extends [Record<string, any>]
    ? {
        component: Component<Props>;
        props(): Props;
      }
    : never
  : never;

const cfg: Record<string, ComponentConfig<{prop1: number}>> = {
  someKey: {
    component: {props: {prop1: 0}},
    props() {
      return {
        prop1: 1
      };
    }
   }
};

Playground Link

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Guide to setting up an interface-only project (along with its dependent projects) on NPM

I'm encountering two problems with my TypeScript project. Imagine I have a interface-only TypeScript project called myproject-api. My plan is to implement the interfaces in two separate projects named myproject-impl1 and myroject-impl2. I am utilizin ...

The text "Hello ${name}" does not get substituted with the name parameter right away in the message string

I'm struggling to get the basic TypeScript feature to work properly. Everywhere I look on the Internet, it says that: var a = "Bob" var message = 'Hello ${a}' should result in a console.log(message) printing "Hello Bob". Howeve ...

Adding a custom property to a React component

Currently, I am facing an issue while attempting to modify an MUI component. Everything works smoothly until I execute the build command, at which point it fails. I have experimented with a few variations of this, but essentially I am looking to introduce ...

Create a fresh array by merging two existing arrays together

My project involves working with two separate arrays. The first array contains normal date values: var = [ "2022-05-01", "2022-05-02", ... "2022-05-30" ] The second array consists of objects that contain s ...

Receiving NULL data from client side to server side in Angular 2 + Spring application

I'm currently working on a project that involves using Angular 2 on the client side and Spring on the server side. I need to send user input data from the client to the server and receive a response back. However, I'm encountering an issue where ...

Using the slice pipe on the data for a child component property is resulting in endless calls to the @Input set method

After incorporating a slice pipe into the data object below and passing that data to the child component's @Input method, there appears to be an endless loop of calls to that method. However, eliminating the slice pipe from the data object resolves th ...

My React JS page suddenly turned blank right after I implemented a setState() function within my functional component

I was working on my code and everything seemed fine until I tried to incorporate the setState function with setcategory and setvalue. However, after making this change, my react page suddenly went blank. Can anyone help me identify what went wrong and pr ...

Issue when trying to use both the name and value attributes in an input field simultaneously

When the attribute "name" is omitted, the value specified in "value" displays correctly. However, when I include the required "name" attribute to work with [(ngModel)], the "value" attribute stops functioning. Without using the "name" attribute, an error ...

The CSS root variable is failing to have an impact on the HTML element's value

I'm in the process of changing the text color on home.html. I've defined the color property in a :root variable, but for some reason, it's not appearing correctly on the HTML page. Take a look at my code: home.scss :root { --prim-headclr : ...

Issue with Typescript not recognizing default properties on components

Can someone help me troubleshoot the issue I'm encountering in this code snippet: export type PackageLanguage = "de" | "en"; export interface ICookieConsentProps { language?: PackageLanguage ; } function CookieConsent({ langua ...

Experimenting with a module reliant on two distinct services

I am facing an issue with a component that relies on a service to fetch data. The service also retrieves configurations from a static variable in the Configuration Service, but during Karma tests, the const variable is showing up as undefined. Although I ...

Tips for maintaining the original data type while passing arguments to subsequent functions?

Is there a way to preserve generic type information when using typeof with functions or classes? For instance, in the code snippet below, variables exampleNumber and exampleString are of type Example<unknown>. How can I make them have types Example& ...

The module '@ngmodule/material-carousel' could not be located

Having an issue with Angular 8 where I am encountering an error when trying to import the @ngmodule/material-carousel module. The specific error message is: Cannot find module '@ngmodule/material-carousel' Package.json "private": true, "depen ...

Error: monaco has not been declared

My goal is to integrate the Microsoft Monaco editor with Angular 2. The approach I am taking involves checking for the presence of monaco before initializing it and creating an editor using monaco.editor.create(). However, despite loading the editor.main.j ...

In the context of Angular, the ELSE statement continues to run even after the IF condition has been satisfied within

Currently, I am utilizing Angular 11 in conjunction with Firestore. Within my code, I am fetching data using the subscribe method from an API service. Subsequently, I am employing a for loop to extract object values in order to verify if a value within a c ...

Leveraging Leaflet or any JavaScript library alongside Typescript and webpack for enhanced functionality

Important: Despite extensive searching, I have been unable to find a resolution to my issue. My current endeavor involves developing a map library through the extension of leaflet. However, it appears that I am encountering difficulties with utilizing js ...

Error: The data type '(number | undefined)[]' cannot be converted to type 'number[]'

Transitioning to Typescript in my NextJS application has presented a challenge that I cannot seem to overcome. The error arises on the line value={value} within the <Slider.Root> The variable value comprises of two numeric elements: a min and a max. ...

The module does not contain 'toPromise' as an exported member in rxjs version 5.5.2

Encountering an error when using toPromise Prior method: import 'rxjs/add/operator/toPromise'; Updated approach: import { toPromise } from 'rxjs/operators'; The new way is causing the following issues: [ts] Module '"d:/.../ ...

Discover the process of accessing and setting values in Angular 8 to easily retrieve and manipulate data from any page!

Greetings! I am currently utilizing Angular 8 and I have a query regarding how to access the set value in any given page. Here is a snippet of my code: class.ts export class testClass { get test():string{ return this.sexe; } ...

Different results are being obtained when destructuring props in a component

Just diving into the world of React and trying to grasp destructuring. I've been doing some reading on it, but I'm currently stuck. When I try to destructure like this function MList({action}) { // const data = [action];}, I only get 'camera ...