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

Unable to retrieve data from the array

I am encountering an issue while trying to fetch data from an array, as I keep receiving undefined Please refer to the image for a visual representation of my problem. I'm not sure what I might be overlooking, so any help would be greatly appreciate ...

Building a database using a dump.sql file in NodeJS (Express) with the added power of TypeScript

I am currently building an application using Express and TypeScript. While the app is already configured to work with MySQL, I am facing a challenge in figuring out how to create the database based on a dump.sql file. CREATE DATABASE IF NOT EXISTS test; U ...

I am having trouble locating my TypeScript package that was downloaded from the NPM registry. It seems to be showing as "module not found"

Having some challenges with packaging my TypeScript project that is available on the npm registry. As a newcomer to module packaging for others, it's possible I've made an error somewhere. The following sections in the package.json appear to be ...

The 'React' namespace does not contain the exported members 'ConsumerProps' or 'ProviderProps'

Is it possible to install this library in Visual Studio with React version 15.0.35? Are there any other libraries that are compatible with this specific React version? import * as React from 'react'; import { RouteComponentProps, NavLink } from ...

Patiently waiting for the component variable to be assigned through subscription

I am facing an issue with two calls in my component. The second call depends on the result from the first call. In the first call, I set the value for my component variable "locked". The second call should only be executed when the result is true, meaning ...

A guide on how to initiate a click event in Angular 5 using JQuery

I am trying to trigger a click event for each element based on its id, but it doesn't seem to be working. Below is the code I am using: ngOnInit() { this.getProductsLists(); } getProductsLists() { this.supplierService.getProductLists() .sub ...

Angular Custom Pipe - Grouping by Substrings of Strings

In my Angular project, I developed a custom pipe that allows for grouping an array of objects based on a specific property: import { Pipe, PipeTransform } from '@angular/core'; @Pipe({name: 'groupBy'}) export class GroupByPipe impleme ...

The Date object in Typescript is represented as a string

My typescript interface includes a variable called start, which is typed as Date | null. This data is retrieved from a dotnet API that returns a DateTime object. The issue arises when the start variable is passed through a function in Date-fns, causing a R ...

Enhancing JavaScript functions with type definitions

I have successfully implemented this TypeScript code: import ytdl from 'react-native-ytdl'; type DirectLink = { url: string; headers: any[]; }; type VideoFormat = { itag: number; url: string; width: number; height: number; }; type ...

The struggle of implementing useReducer and Context in TypeScript: A type error saga

Currently attempting to implement Auth using useReducer and Context in a React application, but encountering a type error with the following code snippet: <UserDispatchContext.Provider value={dispatch}> The error message reads as follows: Type &apos ...

Tips for receiving string body parameters from Express routes in TypeScript instead of using the 'any' type?

I have a situation where I am passing a unique identifier called productId as a hidden input within a form: <form action="/cart" method="POST"> <button class="btn" type="submit">Add to Cart</button ...

Guide on creating a custom command within the declaration of Tiptap while extending an existing extension with TypeScript

I'm currently working on extending a table extension from tiptap and incorporating an additional command. declare module '@tiptap/core' { interface Commands<ReturnType> { table: { setTableClassName: () => ReturnType; ...

Error in sending data to the server via the specified URL: "Http failure response for http://localhost/post.php: 0 Unknown Error" and POST request to http://localhost/post.php failed with error code

Feeling a bit stuck trying to add data to my database. As a junior with PHP and Angular, I am using PHP via XAMPP and Angular 8. Is it possible to create separate files for the post and get methods in the PHP file? app.component.ts import { Component, O ...

Utilizing Typescript for manipulation of Javascript objects

Currently, I am working on a project using Node.js. Within one of my JavaScript files, I have the following object: function Person { this.name = 'Peter', this.lastname = 'Cesar', this.age = 23 } I am trying to create an instanc ...

Tips for validating and retrieving data from a radio button paired with an input box in reactjs

I'm diving into the world of React and facing a challenge with multiple radio buttons that have associated input fields, like in this image: https://i.stack.imgur.com/Upy3T.png Here's what I need: If a user checks a radio button with a ...

Troubleshooting the Issue with Conditional Rendering in Nextjs & TypeScript

Struggling with rendering a component conditionally. I have a drawHelper variable and a function to toggle it between true and false. The component should render or not based on the initial value of drawHelper (false means it doesn't render, true mean ...

Managing absence of ID field in Prisma and retrieving data from API request

When fetching data from an API, my approach looks like this: async function getApiData() { const promises = []; for (let i = 0; i < PAGE_COUNT; i++) { const apiData = fetch(...); } const apiData = await Promise.all(promises); return apiDat ...

Is there a way for me to change the value and placeholder attributes on the Clerk's SignIn component?

Within Clerk's documentation, there is guidance on accessing the input field using the appearance prop as demonstrated below: <SignIn appearance={{ elements: { formFieldInput: 'bg-zinc-300/30' } }}/& ...

Analyzing elements within an array using Angular 4

I have an array filled with various Objects such as: [ {"id":1,"host":"localhost","filesize":73,"fileage":"2018-01-26 09:26:40"}, {"id":2,"host":"localhost","filesize":21,"fileage":"2018-01-26 09:26:32"}, {...} ] These objects are displayed in the fol ...

Sending Component Properties to Objects in Vue using TypeScript

Trying to assign props value as an index in a Vue component object, here is my code snippet: export default defineComponent({ props:{ pollId:{type: String} }, data(){ return{ poll: polls[this.pollId] } } }) Encountering errors wh ...