Deduce the Prop Component Type by Examining the Attribute Type

I am facing an issue with a component that requires a `labels` attribute.

<Component
  defaultValue={FURNITURE.BED}
  labels={[
    {
      value: FURNITURE.BED,
      text: 'Bed',
    },
    {
      value: FURNITURE.COUCH,
      text: 'Couch',
    },
    {
      value: FURNITURE.CHAIR,
      text: 'Chair',
    },
  ]}
  onSubmit={onSubmit}
  ...otherAttributes
/>

Here is the enum and type declaration:

export enum FURNITURE {
  BED = 'bed',
  COUCH = 'couch',
  CHAIR = 'chair'
}

type FurnitureOptions = `${FURNITURE}` // FURNITURE.BED | FURNITURE.COUCH | FURNITURE.CHAIR

const onSubmit = (value: FurnitureOptions} => processFurniture(value)

The issue lies in the fact that the value prop within my Component is set as a string. I would like the Component to infer the type automatically based on the values within the labels, such as FurnitureOptions.

This is how my component is structured:

interface ILabel {
  readonly label: string;
  readonly value: string | number;
}

interface IComponentProps {
  readonly labels: ILabel[];
  readonly defaultValue: string | number | null;
  readonly onSubmit: (value: ILabel) => void;
  ...otherAttributes

const Component = ({
  labels, default, onSubmit, ...otherAttributes
}: IComponentProps => { ...function code }

My desired outcome is to replace string | number with any type parameter, like FurnitureOptions.

I have attempted using a type parameter T, calling the component with

<Component<FurnitureOptions> ... />
, but this solution feels messy and goes against coding standards. I am looking for a way to achieve this inference automatically.

One approach I have tried involves extracting possible values like so:

type PossibleValues = Extract<typeof labels[number], { readonly [v in 'value']: unknown }>['value'] // FURNITURE.BED | FURNITURE.COUCH | FURNITURE.CHAIR

Unfortunately, I cannot pass this into the Component parameters without encountering a circular error.

'labels' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. ts(7022)

Are there any other techniques available for me to accomplish this?

Answer №1

Utilizing generics is essential in this scenario.

We'll begin by defining a generic type for your label:

interface ILabel<T extends string | number> {
  readonly label: string;
  readonly value: T;
}

With this, you can now do:

type ILabelTest = ILabel<FURNITURE>

This will result in a type that specifies only certain value types are allowed for your labels.


The next step involves incorporating generic props.

interface IComponentProps<T extends string | number> {
  readonly labels: ILabel<T>[];
  readonly defaultValue: T | null;
  readonly onSubmit: (value: ILabel<T>) => void;
}

Now the props can accept a generic type to be passed into both the labels and onSubmit.


Lastly, make your component generic so that the generic type can be supplied to your props.

const Component = <T extends string | number>({
  labels,
  defaultValue,
  onSubmit,
}: IComponentProps<T>) => {
  //...
  return <></>
}

By following these steps, your code will function as expected:

const furnitureTest = <Component
  defaultValue={FURNITURE.BED}
  labels={[
    {
      value: FURNITURE.BED,
      label: 'Bed',
    },
    {
      value: FURNITURE.COUCH,
      label: 'Couch',
    },
    {
      value: FURNITURE.CHAIR,
      label: 'Chair',
    },
  ]}
  onSubmit={(item) => {
    console.log(item.value) // item.value is type: FURNITURE
  }}
/>

Furthermore, it will also work seamlessly with other label values:

const letterTest = <Component
  defaultValue="a"
  labels={[
    { label: 'A', value: 'a' },
    { label: 'B', value: 'b' },
  ]}
  onSubmit={item => item.value} // item.value is type: 'a' | 'b'
/>

See Playground

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

Is it possible to implement drag and drop functionality for uploading .ply, .stl, and .obj files in an angular application?

One problem I'm facing is uploading 3D models in angular, specifically files with the extensions .ply, .stl, and .obj. The ng2-upload plugin I'm currently using for drag'n'drop doesn't support these file types. When I upload a file ...

Tips for defining the type of any children property in Typescript

Currently, I am delving into Typescript in conjunction with React where I have a Team Page and a slider component. The slider component is functioning smoothly; however, my goal is to specify the type of children for it. The TeamPage import react, { Fragm ...

Customizing the Material UI v5 theme with Typescript is impossible

I'm attempting to customize the color scheme of my theme, but I am encountering issues with accessing the colors from the palette using theme.palette. Here is a snippet of my theme section: import { createTheme } from "@mui/material/styles&qu ...

Unable to utilize vue-cookies library in TypeScript environment

I have integrated vue-cookies into my app in the main.ts file: import VueCookies from 'vue-cookies'; ... const app = createApp(App) .use(IonicVue) .use(router) .use(VueCookies,{ expires: '30d', }); Despite adding the cookie v ...

Updating a subscribed observable does not occur when pushing or nexting a value from an observable subject

Help needed! I've created a simple example that should be working, but unfortunately it's not :( My onClick() function doesn't seem to trigger the console.log as expected. Can someone help me figure out what I'm doing wrong? @Component ...

Number as the Key in Typescript Record<number, string> is allowed

As a newcomer to TypeScript, I am still learning a lot and came across this code snippet that involves the Record utility types, which is quite perplexing for me. The code functions correctly in the playground environment. const data = async (name: string ...

A tutorial on ensuring Angular loads data prior to attempting to load a module

Just starting my Angular journey... Here's some code snippet: ngOnInit(): void { this.getProduct(); } getProduct(): void { const id = +this.route.snapshot.paramMap.get('id'); this.product = this.products.getProduct(id); ...

What is the process for extracting the background color from a typescript file in Angular and connecting it to my HTML document?

My typescript array population is not changing the background color of my div based on the values in the array. I've attempted to set the background using [style.backgroundColor]="statusColor[i]", where statusColor is an array declared in my typescrip ...

Utilizing client extension for Postgres with Prisma to activate RLS: A step-by-step guide

Recently, I attempted to implement client extension as advised on Github. My approach involved defining row level security policies in my migration.sql file: -- Enabling Row Level Security ALTER TABLE "User" ENABLE ROW LEVEL SECURITY; ALTER TABLE ...

Validators in Angular forms are a powerful tool for enforcing

Is it possible to use Validators in the ts.file to display an error message when a field is invalid, rather than directly in the html? Thanks. html <form [formGroup]="form"> <mat-form-field> <mat-label>Nom</mat-label> ...

Discovering the ASP.NET Core HTTP response header in an Angular application using an HTTP interceptor

I attempted to create a straightforward app version check system by sending the current server version to the client in the HTTP header. If there's a newer version available, it should trigger a notification for the user to reload the application. Ini ...

Utilize key-value pairs to reference variables when importing as a namespace

Is it feasible to utilize a string for performing a lookup on an imported namespace, or am I approaching this the wrong way? Consider a file named my_file.ts with contents similar to: export const MyThing: CustomType = { propertyOne: "name", ...

A guide on passing variables to the MUI styled function within ReactJS

Is it possible to pass a variable directly to the styled function in order to conditionally change style properties while using MUI styled function? I want to achieve something like this: borderColor: darkMode ? 'white' : 'black' const ...

Receiving an error stating "module not found" when attempting to retrieve the NextAuth session using EmailProvider in getServerSideProps

Trying to access the NextAuth session from a server-side call within getServerSideProps, using an EmailProvider with NextAuth. Referring to an example in NextAuth's documentation, I'm attempting to retrieve the session from getServerSideProps. T ...

Encountering an "Invalid hook call error" while utilizing my custom library with styled-components

I recently developed my own custom UI tool using the styled-components library, integrating typescript and rollup for efficiency. One of the components I created looks like this: import styled from 'styled-components' export const MyUITest2 = s ...

When configuring the Redux logger, the type 'Middleware<{}, any, Dispatch<UnknownAction>>' is not compatible with type 'Middleware<{}, any, Dispatch<AnyAction>>'

In my React project, I have defined the redux logger with the package version "redux-logger": "^3.0.6" in the file store.ts as shown below: import { configureStore } from '@reduxjs/toolkit'; import rootReducer from '@/re ...

Unable to retrieve information from a function in Vue.js (using Ionic framework)

When attempting to extract a variable from a method, I encounter the following error message: Property 'commentLikeVisible' does not exist on type '{ toggleCommentLikeVisible: () => void; This is the code I am working with: <template& ...

Utilizing Javascript to load and parse data retrieved from an HTTP request

Within my application, a server with a rest interface is utilized to manage all database entries. Upon user login, the objective is to load and map all user data from database models to usable models. A key distinction between the two is that database mode ...

Caution: The `id` property did not match. Server: "fc-dom-171" Client: "fc-dom-2" while utilizing FullCalendar in a Next.js environment

Issue Background In my current project, I am utilizing FullCalendar v5.11.0, NextJS v12.0.7, React v17.0.2, and Typescript v4.3.5. To set up a basic calendar based on the FullCalendar documentation, I created a component called Calendar. Inside this comp ...

Angular nested innerHTML not evaluating ternary operator

Here is a code snippet that I am struggling with: {{ Name && Name.length > 20 ? (Name | slice: 0:20) + "..." : Name }} The above code works fine when used inside a div, but when I try to use it within innerHTML, I encounter a syntax e ...