Associate enumerated values with specific subclass types in Typescript

In my Typescript and Redux project, I am looking for a way to enforce type checking for the action parameter of a reducer based on its type. This concept is similar to how Entity Framework maps an enum column to a specific subclass.

Here is an example that I'm working on:

enum ActionType {
  doThing1,
  doThing2      
}

interface Action {
  readonly type: ActionType
}

interface Thing1Action extends Action {
   readonly payload: Thing1Payload;
}

interface Thing2Action extends Action {
   readonly payload: Thing2Payload;
}


interface State {
   readonly thing1: Thing1Payload;
   readonly thing2: Think2Payload;
}

const initialState: State = {
   thing1: null,
   thing2: null,
}

function reducer(state = initialState, action: Action): State {
    switch (action.type)
    {
        case ActionType.doThing1: 
            return {...state, thing1: action.payload };
    }
    return state;
}

In the code above, I want to restrict the action.payload to only hold a Thing1Payload. Currently, Typescript gives me errors stating that action does not have the property payload or assigns it as any when I don't specify the type for action.

I am specifically interested in achieving this kind of validation during both coding and compiling stages.

Answer №1

A tagged union is the term used to describe this concept. Within each interface that extends Action, you must redeclare the type field, but you should assign it a specific enum literal type (which essentially means specifying only one possible enum value as the type of the field).

You then create a union type comprising all the extended interfaces and use it as the parameter type. Utilizing a switch statement on the type acts as a type guard.

enum ActionType {
    doThing1,
    doThing2
}

interface Action {
    readonly type: ActionType
}

interface Thing1Payload {
    b: number
}

interface Thing2Payload {
    a: number
}

interface Thing1Action extends Action {
    readonly type: ActionType.doThing1 // redefining the field with only ActionType.doThing1 as an assignable value
    readonly payload: Thing1Payload;
}

interface Thing2Action extends Action {
    readonly type: ActionType.doThing2 // redefining the field with only ActionType.doThing2 as an assignable value
    readonly payload: Thing2Payload;
}

interface State {
    readonly thing1: Thing1Payload;
    readonly thing2: Thing2Payload;
}

const initialState: State = {
    thing1: <Thing1Payload>null,
    thing2: <Thing2Payload>null,
}

// Interface containing all actions
type AllAction = Thing1Action | Thing2Action;

function reducer(state = initialState, action: AllAction): State {
     // This switch now serves as a type guard
    switch (action.type) {
        case ActionType.doThing1:
            // action will be typed as Thing1Action
            return { ...state, thing1: action.payload };
        case ActionType.doThing2:
            // action will be typed as Thing2Action
            return { ...state, thing2: action.payload };
    }
    return state;
}

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

Error TS2339: Cannot find property 'posts' in the given type

This is my code where I am attempting to make an API call in Ionic 5 using Axios import axios from "axios"; import { IonCard, IonCardContent, IonCardSubtitle, IonCardTitle } from "@ionic/vue"; export default { name: "Ta ...

Is there a way to declare the different types of var id along with its properties in Typescript?

I recently received a task to convert a JavaScript file to a TypeScript file. One issue I am currently facing is whether or not I should define types for the 'id' with this expression, e.g., id={id}. So far, I have tried: Even though I defined ...

Designing a TypeScript class that incorporates an interface

Struggling to grasp the correct syntax, I am looking to incorporate an interface into my project. The desired interface for implementation is as follows: interface Test { [name : string] : (source : string) => void; } My understanding is that this ...

Nativescript encountered an error regarding faker: module './address' not found

Currently in the process of learning nativescript, I am experimenting with using faker to generate some data while working with typescript. Here are the versions I am using: Node - 6.9.4 Faker - 3.1.0 Typescript - 2.1.4 Encountered an error which i ...

Unable to simulate Paginate function in jest unit testing

Currently, I am in the process of mocking the findAll function of my service. To achieve this, I have to decide whether to mock the repository function findAndCount within myEntityRepository or the paginate function of the nestjs-typeorm-paginate node modu ...

Tips for managing the dimensions of the <label> element within a React component

I'm facing an issue where the element size is not matching the box size as expected. Additionally, the width property doesn't seem to work in React. Does anyone know how to solve this problem? const DragDrop = () => { ... return ( &l ...

The conversion of an array to Ljava/lang/Object is not possible

I'm currently working on a project using NativeScript app with TypeScript where I am trying to pass an array of android.net.Uri to a function. However, when attempting to do so, I encounter an error mentioning that the 'create' property does ...

What could be causing MongoDB to not delete documents on a 30-second cycle?

Having trouble implementing TTL with Typegoose for MongoDB. I am trying to remove a document from the collection if it exceeds 30 seconds old. @ObjectType("TokenResetPasswordType") @InputType("TokenResetPasswordInput") @index( { cr ...

Convert all existing objects to strings

I have a type that consists of properties with different data types type ExampleType = { one: string two: boolean three: 'A' | 'Union' } Is there an easier way to define the same type but with all properties as strings? type Exam ...

Issue with Angular and OIDC - user data is missing upon logging in

I am in the process of developing an application using Angular along with IdentityServer as the Single Sign-On (SSO) provider in ASP.NET. After successfully logging in and retrieving user information from the authentication server: User info The followin ...

Having trouble injecting ActivatedRouteSnapshot into the component

Struggling to inject ActivatedRouteSnapshot into a component, encountering errors when trying to access query params. Here is the error stack trace: "Error: Can't resolve all parameters for ActivatedRouteSnapshot: (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?). a ...

Accessing property values from a map in Angular

Is there a way to retrieve a property from a map and display it in a table using Angular? I keep getting [object Object] when I try to display it. Even using property.first doesn't show anything. //model export interface UserModel { room: Map ...

The uploaded files are not supported due to a media type error (415)

Although I found a similar query, it didn't provide a solution to the error I am encountering. Below is the PUT action in my API's Controller, which functions correctly in Swagger: [HttpPut("{id}/upload")] public async Task<IActionResult> ...

"Utilizing aws-sdk in a TSX file within a React project: a step-by

When working on a project using TypeScript (tsx) for React, I encountered an issue with uploading images to the server using aws-sdk to communicate with Amazon S3. To resolve this, I made sure to install aws-sdk via npm and typings. UploadFile.tsx import ...

Geometric structures in the style of Minecraft: Hexagonal Voxel Design

I'm attempting to create a hexagonal map by following the example at . Is there a way to generate hexagons instead of cubes? Using the first option from the manual resulted in creating a hexagonal mesh and positioning it. However, the second option ...

Tips for eliminating the gap between digits and symbols in an OutlinedTextField within the Material Ui framework

Using material Ui OutlinedTextField with the code snippet below import { List, styled, Switch, TextField, Theme, withStyles } from '@material-ui/core'; export const OutlinedTextField = withStyles((theme: Theme) => ({ root: { '& ...

Proper application of this - encountering issues with property emit of undefined in Angular

I am working with an angular component and encountering the following code: @Output() fixPercentChanged = new EventEmitter<number>(); In addition, I have this event: fixChanged(e) { setTimeout(() => { let fix = e.component.option(&apo ...

Invoking a function from a collection of mixed data types

I have established a mapping for a discriminated union consisting of different types, each linked to a corresponding function that uses a member of the union as a parameter: export interface Truncate { type: 'truncate' maxLength: number } ex ...

Is there a lack of compile checking for generics in Typescript?

Consider the code snippet below: interface Contract<T> { } class Deal<D> implements Contract<D> { } class Agreement<A> implements Contract<A> { } Surprisingly, the following code compiles without errors: let deal:Contract ...

The combination of TypeScript output is not working as expected

Here is my current configuration: tsconfig.json { "compileOnSave": true, "compilerOptions": { "module": "none", "outFile": "js/app.js" } } MyApp.Main.ts class Main { constructor() { Utilities.init(this); ...