Union types discriminate cases within an array

Creating a union type from a string array:

const categories = [
  'Category A',
  'Category B'
] as const

type myCategory = typeof categories[number]

myCategory is now 'Category A' | 'Category B'

Now, the goal is to create a discriminated union:

type categoryA = {
  type: 'Category A'
  // more properties
}

type categoryB = {
  type: 'Category B'
  // more properties
}

type selectedCategory = categoryA | categoryB

The aim here is to restrict the usage of cases in myCategory for the discriminated union - is this achievable?

Update: Ensuring that the cases in selectedCategory align with the values in categories. Currently, assigning any arbitrary string value to type of categoryA or categoryB is possible

Avoiding the use of interfaces or classes.

This is being done to enable data validation and validate oneOf<myCategory> (pseudo code) for selectedCategory.

Answer №1

It appears that the goal is to map over a union type to create a discriminated union, allowing for custom properties to be attached to each member of the union.

To achieve this, one can leverage distributive conditional types to distribute across the union type.

Here's an example:

const options = [
  'Option1',
  'Option2'
] as const

type myOption = typeof options[number]

type Distribute<U> = U extends any
  ? U extends "Option1"
    ? { option: U, prop1: number }
    : U extends "Option2"
      ? { option: U, prop2: boolean }
      : never
  : never


type Discriminated = Distribute<myOption>

This results in a union with the following structure

{
    option: "Option1";
    prop1: number;
} | {
    option: "Option2";
    prop2: boolean;
}

It is assumed that the additional properties of each union member should differ, otherwise a discriminated union may not be necessary. Keep in mind that when adding new members to myOption, they must be accounted for within the Distribute type.

Answer №2

When attempting to utilize the third type, an error arises; however, no error occurs when adding it to typeAorB. A similar issue is present in bugs' solution, albeit being more innovative than the description provided below. It would have been preferable if an error was triggered in that scenario.

To address this, I introduced a base type:

type baseTypeAorB = {
    type: myType;
}

This base type was then integrated into typeAorB:

type typeAorB = baseTypeAorB & (typeAAA | typeBBB)

Subsequently, despite there not being an error thrown, upon introducing typeCCC into the mix:

type typeCCC = {
  type: 'CCC'
  // additional properties
}

type typeAorB = baseTypeAorB & (typeAAA | typeBBB | typeCCC) // No error 

It is worth noting that the resultant typeAorB does not include typeCCC; inspection in the playground reveals its composition as follows:

type typeAorB = (baseTypeAorB & typeAAA) | (baseTypeAorB & typeBBB)

baseTypeAorB effectively prevents the inclusion of typeCCC within the type. Consequently, any attempt to use it as if it contains typeCCC will result in an error:

let x: typeAorB = {
  type: "CCC" // An error will be displayed here 
}

View on Playground: 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

The name 'console' could not be located

I am currently working with Angular2-Meteor and TypeScript within the Meteor framework version 1.3.2.4. When I utilize console.log('test'); on the server side, it functions as expected. However, I encountered a warning in my terminal: Cannot ...

Implementing TypeScript type definitions for decorator middleware strategies

Node middlewares across various frameworks are something I am currently pondering. These middlewares typically enhance a request or response object by adding properties that can be utilized by subsequent registered middlewares. However, a disadvantage of ...

Adding connected types to a list using Typescript

Question regarding Typescript fundamentals. In my code, I have a list that combines two types using the & operator. Here is how it's initialized: let objects: (Object & number)[] = []; I'm unsure how to add values to this list. I attem ...

Integrating a non-nullable static type into memoized components leads to a lint error - refer to the example provided for

Example 1: When defining a non-nullable someStaticProperty, a lint error will be thrown: import { NamedExoticComponent, memo } from "react"; type WithComponentId = { componentId: string }; type ScreenComponentStaticMembers = { someStaticProperty: str ...

The MUI theme seems to be missing its application

As a newcomer to MUI, I'm facing challenges when trying to apply a custom theme. My goal was to create a new variant for the button using the code snippet below: // @ts-nocheck import React, {FC} from 'react'; import { createTheme, ThemeProv ...

Storing input values in the state using Typescript by default

Upon launching, my activeField state is initially empty. However, when a user focuses on the field, it gets added to the state. I am encountering a warning in Typescript because when I attempt to update the selectionEnd of that field, it tells me: Property ...

Developing a search feature using Angular 6 with Observable subscription for the FrontEnd application

I have a unique challenge where I need to implement a full text search in the FrontEnd due to restrictions with the API. When the frontend starts up, it fetches all data entries from the Backend and subscribes them inside a component using an async pipe. T ...

Two services declared with "providedIn: 'root'" that have identical names

Imagine if there are two distinct services in two separate project categories, both sharing the same name. /app/services/category1/my.service.ts: @Injectable({ providedIn: 'root' }) export class MyService { foo() { return 'foo&apo ...

Issue with Angular MatSelect Losing Selected Value in Reactive Form Upon Submission

Working on an Angular project with a reactive form that features a <mat-select> for selecting cities. Although the dropdown functions properly in displaying and allowing city selection, there's a problem when attempting to submit the form: the s ...

The property "props" is not recognized within the context of type PropType

Within my component, I am receiving a prop ("author") from a parent component. Although I have defined the prop type as "AuthorProps", I am getting an error stating Property 'author' does not exist on type 'AuthorProps', even though the ...

Is there a way to access URL parameters in the back-end using Node.js?

How can I extract querystring parameters email, job, and source from the following URL? I want to use these parameters in my service class: @Injectable() export class TesteService{ constructor(){} async fetchDataFromUrl(urlSite: URL){ ...

Issue encountered: Jest-dom is throwing a TypeError because $toString is not recognized as a function on a project using Typescript, React

I have been facing a challenge while setting up jest and @testing-library/jest-dom for my typescript/react/next.js website. Each time I try running the tests, an error occurs, and I am struggling to identify the root cause. This issue has been perplexing ...

How to replace/redirect the import statement in TypeScript from { X } to 'Y'

My situation involves an external library known as Y, which was installed using npm and loaded from the node_modules directory. This library is hosted on GitHub and currently being utilized in my project in the following manner: import { X } from 'Y& ...

Having trouble entering text into a React input field

Encountering a puzzling issue with a simple form featuring an input field that inexplicably won't respond to keyboard typing. Initially, suspicions pointed towards potential conflicts with the onChange or value props causing the input to be read-only. ...

The initial update of the view does not occur when a component property changes in Angular 2 RC6

I am currently facing an issue with a component in my project. This component calls a service to retrieve locally stored JSON data, which is then mapped to an array of objects and displayed in the component view. The problem I am encountering is that the v ...

How can this be happening? It's expected that items will be printed, but for some reason

I'm struggling to figure out why the console.logs aren't showing up. import { GenericRepository, getGenericRepository } from '../src/database/repository/GenericRepository'; import { start, stop } from '../src/index'; import r ...

An issue arises in Slate.js when attempting to insert a new node within a specified region, triggering an error

A relevant code snippet: <Slate editor={editor} value={value} onChange={value => { setValue(value); const { selection } = editor; // if nothing is currently selected under the cursor if (select ...

The navigation function in Angular, this.router.navigate, is causing issues and

I've encountered a peculiar issue. There's a logout function that is activated whenever I receive a 401 response from any API I interact with. The function looks like this: constructor( private router: Router, ) {} logout(router1: Router ...

crafting connections in 3D using TypeORM (ORM)

I attempted to construct a database schema involving users, groups, documents, and permissions. Users can be part of multiple groups Groups can have multiple users Users can possess permissions for documents Groups can have permissions for documents Perm ...

What situations call for the use of 'import * as' in TypeScript?

Attempting to construct a cognitive framework for understanding the functionality of import * as Blah. Take, for instance: import * as StackTrace from 'stacktrace-js'; How does this operation function and in what scenarios should we utilize imp ...