Determining type properties dynamically depending on the value of another property

My goal is to create a type that ensures the correct usage of the DynamicColor type.

enum ColorsEnum {
  red = "red",
  green = "green",
  blue = "blue",
  yellow = "yellow",
}

type ColorsMapperType = {
  type: ColorsEnum
  red: {
    redProperty: string
  }
  green: {
    greenProperty: string
  }
  blue: {
    blueProperty: string
  }
  yellow: {
    yellowProperty: string
  }
}

type ColorsMapper = Omit<ColorsMapperType, "type">

export type DynamicColor = {
  [ColorType in keyof ColorsMapper]: {
    [Prop in 'type' | ColorType]: Prop extends 'type' ? ColorType : ColorsMapper[ColorType]
  }
}[keyof ColorsMapper]

Codesandbox: https://codesandbox.io/s/naughty-sanderson-niqez?file=/src/index.ts

I have managed to make it mostly functional. However, I want the type field within the DynamicColor type to be of the correct ColorEnum type.

Currently, I receive autocomplete suggestions for the string values of ColorEnum, rather than the actual enum type.

For instance, the following code should be considered valid:

const color: DynamicColor = {
  type: ColorsEnum.green,
  green: {
    greenProperty: "1",
  },
}

On the other hand, none of the following pieces of code should pass validation:

const color0: DynamicColor = {
  type: "green", // invalid because it doesn't match the ColorsEnum type
  green: {
    greenProperty: "1",
  },
}

const color1: DynamicColor = {
  type: ColorsEnum.blue, // invalid because the property "blue" is missing
}

const color2: DynamicColor = {
  type: ColorsEnum.blue, // invalid because the color property does not align with the correct type (ColorsEnum.blue requires a "blue" property)
  green: {
    greenProperty: "1",
  }
}

const color3: DynamicColor = {
  type: ColorsEnum.blue, // invalid due to having more than one color property
  blue: {
    blueProperty: "1",
  }
  green: {
    greenProperty: "1",
  }
}

Is there a method to achieve this? Thank you in advance, and any suggestions for enhancing this code are welcome.

Answer №1

To convert all the elements in the ColorsEnum to a different type, you can define it like this (in pseudocode)

{ type: T; [T]: ColorsMapper[T] }
. This can be achieved using conditional types. Here is an example solution that I came up with.

enum ColorsEnum {
  red = "red",
  green = "green",
  blue = "blue",
  yellow = "yellow",
}

type ColorsMapper = {
  type: ColorsEnum
  red: {
    redProperty: string
  }
  green: {
    greenProperty: string
  }
  blue: {
    blueProperty: string
  }
  yellow: {
    yellowProperty: string
  }
}
type MapToColorType<T> = T extends ColorsEnum ? { [key in T]: ColorsMapper[T] } & { type: T } : never;
type Result = MapToColorType<ColorsEnum>;

const color: Result = {
  type: ColorsEnum.green,
  green: {
    greenProperty: "1",
  },
}

You can experiment with it here.

Explanation

Conditional types are distributive on union types, meaning the condition applies to all members of the union. Enums represent a union of all possible values, allowing us to map each enum member to a specific type effectively.

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

A step-by-step guide on updating a deprecated typings package manually

Currently, I am developing a NodeJS application using TypeScript and incorporating numerous Node packages. However, not all of these packages come with TypeScript definitions, so Typings is utilized to obtain separate definition files. During the deployme ...

Extract array values from object properties

Is there a way to destructure an object and assign its properties into an array instead of as variables? For instance: const obj = { a: 1, b: 2 }; const { a, b } = obj; const arr = [a, b]; The above method works fine, but it involves redefining the vari ...

Components in Angular 4 that are loaded dynamically using attribute directives are enclosed within a <div> element

My goal is to dynamically generate components based on the configuration options, specifically creating a toolbar with different "toolbar items". Following the Angular guide at: https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html, I h ...

Navigate to the initial error on a form submission in a Reactjs application containing numerous form fields

I am working on a project using React along with Material UI and TypeScript, where I have implemented a form. Upon form submission, if there are validation errors in any input fields, I would like the page to automatically scroll to the first input field w ...

React.Context is associated with a universal module definition global variable

I have just created a new context.tsx file containing the following code: import { createContext } from 'react'; export interface LibsAndComponentsInterface { data: unknown[]; } export const LibsAndComponentsContext = createContext< Libs ...

Unable to complete plugin registration due to a "handler missing or undefined" error

I'm in the process of developing a sample nodejs application that includes a plugin. However, when I attempt to run the application, I encounter an error stating "Missing or undefined handler". Here is my plugin file: exports.plugin = { name: "t ...

Having trouble with a Parsing Syntax Error related to "Export Default" in React Native Typescript?

I am encountering an issue with my React Native project when transpiling Typescript code. The error occurs in the simulator during build, and seems to be related to using export default in Typescript for component export. This error arises as a parsing iss ...

The concept of nesting partial types in Typescript

Struggling to type partial objects from GraphQL queries, especially with an object that looks like this... // TypeScript types interface Foo { bar: Bar } interface Bar { a: number, b: number } // GraphQL query foo { bar { a // 'b&a ...

Incorporate Canvg version 4 into a TypeScript project

I am currently facing an issue with an older TypeScript project that has the following tsconfig setup: { "compilerOptions": { "baseUrl": "./src", "outDir": "build/dist", "module": &q ...

Ensure that any modifications made to an Angular service are reflected across all components that rely on that service

I am currently in the process of replicating a platform known as Kualitee.com, which serves as a test-management tool utilized by QA Engineers. Within Kualitee, users can access multiple projects, each containing various test cases and team members. The ab ...

Is there a simpler and more refined approach for handling Observables within RxJS pipelines?

Picture this: I have an observable that gives me chocolate cookies, but I only want to eat the ones without white chocolate. Since I am blind, I need to send them to a service to determine if they are white or not. However, I don't receive the answer ...

What is the best way to retrieve all the keys from an array?

I am looking to retrieve the address, latitude, and longitude data dynamically: let Orders= [{ pedido: this.listAddress[0].address, lat: this.listAddress[0].lat, lng: this.listAddress[0].lng }] The above code only fetches the first item from the lis ...

Top-notch approach for consolidating Typescript Declaration files into a single comprehensive file

Is there any efficient way to package declaration files together without using the module declaration approach? declare module "path/to/file" { ... } declare module "path/to/file/sub/file" { ... } and so on. I have encountere ...

Tips on how to reach an Angular component within a third-party library

I'm currently working on a web application that incorporates the Deezer player through the official JS SDK. I've run into an issue where I am unable to access my Angular component from within the Deezer event subscription. The arrow function does ...

Struggling with TypeScript and JsObservable? Let us assist you!

Having previous experience with JSRender, JSViews, and JSObservables, I recently embarked on a new project using TypeScript. Unfortunately, I am struggling to understand how to properly utilize TypeScript in my project, especially when it comes to referenc ...

What is the difference between adding the .js extension when importing files in TypeScript versus Angular imports?

When working on my TypeScript project, I've encountered an issue where I need to include the *.js extension when importing files. If I omit the extension, the project will build successfully but fail at runtime in the browser due to file not found err ...

A special function designed to accept and return a specific type as its parameter and return value

I am attempting to develop a function that encapsulates a function with either the type GetStaticProps or GetServerSideProps, and returns a function of the same type wrapping the input function. The goal is for the wrapper to have knowledge of what it is ...

Customized object property names for an array of generic object types

I am working with an object in Typescript that has a data property structured like this: type MyThing = { data: { options: { myKey: string, myValue: string }[], key: 'myKey', value: 'myValue' } } I want ...

Utilizing Typescript for Axios Response

Incorporating Typescript into my project, I encountered a tedious issue while making an API call using axios. The problem lies within handling nested data properly. Despite my belief that I have correctly typed everything, I persistently face a Typescript ...

Error in ThreeJS: Unable to execute material.customProgramCacheKey

I encountered an issue TypeError: material.customProgramCacheKey is not a function The error pops up when I invoke the function this.animate(). However, no error occurs when the URL is empty. Where could this error be originating from since I don't ...