Ensuring thoroughness in validation without the use of specific text strings

Implementing the assignment or assertion of never at the end of a function is a strategy commonly used in Typescript to ensure exhaustive checks at compile time.

To enable the compiler to recognize this, explicit strings are needed for it to check against when determining if the function returns definitively before the assignment/assertion of never.

Is there a way to introduce a typed variation of Object.freeze that specifically applies to object literals and further up the chain? This would allow for something like the following:

Even more ideally, could an interface be created where the keys automatically match those of each Action.type (as shown in this example)? If such an interface existed, declaring actionMap as that interface could enforce the compile-time check.

Both methods offer solutions to the same issue... given only a discriminated union, is there a method to perform exhaustiveness checks like this at compile time without relying on explicit strings within the function?

interface Increment {
    type: 'increment'
}

interface Decrement {
    type: 'decrement'
}

type Action = Increment | Decrement

const inc: Increment = { type: 'increment' };
const dec: Decrement = { type: 'decrement' };

//this illustrates a Typescript variation
const actionMap = Object.freeze({
    [inc.type]: n => n + 1,
    [dec.type]: n => n-1
});


function doAction(action: Action, val: number): number {

    if(actionMap[action.type]) {
        return actionMap[action.type](val);
    }

    //this would result in a compile time error if the above check failed 
    const _exhaustiveCheck: never = action;
}

console.log(doAction(inc, 1));
console.log(doAction(dec, 1));

Answer №1

If you want to create a map that covers all cases in a discriminated union, there is a simple approach you can take. Just make sure that the keys of the map match the identifier of the discriminated union.

type ActionMap = {
    [P in Action["type"]]: (val:number)=>number
}; 

To implement this interface, you can follow this example:

var map: ActionMap = {
   decrement: n => n - 1,
   increment: n=> n + 1
}

Note: I have discovered a more flexible solution that not only types the keys of the discriminated union values but also allows you to type the payload.

Step One: Define your union using key:type pairs for better readability.

type Actions = {
    "increment": { incrementValue: number }
    "decrement": { decrementValue: number }
}

Step Two: Create an Action Discriminated Union from the key-value map. It may look complex, but it essentially combines all key-value pairs into a Discriminated Union type.

type Action = {
    [P in keyof Actions]: { type: P } & ActionsMap[P]
}[keyof Actions];

Step Three: Define a type for your map

type ActionsMap = {
    [P in  keyof Actions]: (val:number, action:Actions[P])=>number
}

Step Four: Enjoy the benefits of your fully type-safe action/reducer map!

const map:ActionsMap = {
    decrement: (val, action) => val + action.decrementValue,
    increment: (val, action) => val + action.incrementValue,
}

Be cautious though, as this approach pushes the boundaries of what typescript can handle and may be subject to changes in future versions.

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

Merging Type-GraphQL and Typegoose through a Variety of Decorators

Using a combination of Type-GraphQl and Typegoose, I aim to streamline my data definitions by consolidating them into one source for both GraphQL schemas and Mongoose queries. Is it feasible to merge the two libraries in a way that allows me to describe bo ...

Step by step guide on serializing two forms and an entire table

I have been attempting to serialize data from two forms and the entire table simultaneously. While the form data is successfully serialized, I am encountering difficulty in serializing the table records as well. Below is the code snippet of what I have att ...

Creating Beautiful Tabs with React Material-UI's Styling Features

I've been delving into React for a few hours now, but I'm struggling to achieve the desired outcome. My goal is to make the underline color of the Tabs white: https://i.stack.imgur.com/7m5nq.jpg And also eliminate the onClick ripple effect: ht ...

Switching Bootstrap Navbar Active State with JavaScript

I have encountered an issue with using the "active" class on my navbar navigation items in Bootstrap 4. When I click on the links, the active state does not switch as intended. I have tried incorporating JavaScript solutions from similar questions but have ...

Ways to ensure that a function operates independently for each cloned div and not the original

I'm facing an issue where a jquery function needs to be executed when the drop-down is changed. However, the problem arises when I clone the div element and make changes in the drop-down of the newly cloned element, the effect takes place in the first ...

Assistance with the Chakra UI Range Slider in React

I could use some help figuring out where exactly to implement my OnChange and Map functions for mapping JSON data into a Range Slider. Everything seems to be rendering correctly, but I keep encountering an error when I try to move the slider: Unhandled Ru ...

Inquiry regarding modules in Javascript/Typescript: export/import and declarations of functions/objects

I'm fresh to the realm of TypeScript and modules. I have here a file/module that has got me puzzled, and I could really use some guidance on deciphering it: export default function MyAdapter (client: Pool): AdapterType { return { async foo ( ...

Using HTML and C# to Deliver Emails

I've encountered a challenge with my HTML page that includes a textbox for users to input their email. When the submit button is clicked, an email should be sent to a specific email address defined in the code, and a pop-up box indicating "Email Sent" ...

Encountering issues with the routing in my live Node.js project: ERROR

I am encountering issues with my project in production. I suspect it could be due to a misconfiguration or something similar. Could you please review the provided code snippets and see if you notice any potential issues? The project works fine locally, bu ...

Encountering issues with the Sequelize Model.prototype.(customFunction) feature malfunctioning

While attempting to define a customFunction within the Sequelize model, I encountered an error: TypeError: user.getJWT is not a function at User.create.then (/projects/test/a/app/controllers/UserController.js:22:29) Below is the code snippet from ...

Changing a single variable into an array that holds the variable in JavaScript

Is there a way to change 5 into [5] using JavaScript? I need this functionality for a method that utilizes jQuery's $.inArray. It should be able to handle both scalar variables and arrays, converting scalars into arrays with a single element. ...

How to Effortlessly Populate Cascading Dropdowns in ASP.Net MVC 5 using a Reusable

I am currently working on an ASP.Net MVC 5 application that has multiple edit pages. Each edit page consists of various input elements such as textboxes, checkboxes, and dropdowns. I want to implement a functionality where the values of one dropdown are ch ...

Angular Translate - Utilizing translate-values attribute for translation

Having trouble using angular translate with dynamic translation values that need to be translated first. If you want a better explanation of the issue, check out this plunker: PLUNKER <p translate="PARAGRAPH" translate-values="{username: ('us ...

Send PS3 through user agent for redirection

If a user is accessing the site using a PS3, I want them to be redirected to a different webpage Below is the code snippet I have been attempting to use: <script language=javascript> <!-- if ((navigator.userAgent.match(/iMozilla/i)) || (navigato ...

Mastering the Art of jQuery Post with Iteration and Result Utilization

I am facing an issue with my code function fetchInfoFromDB() { let body = ""; let text = ""; $("tr").each(function (index) { let code = $(this).children("td:nth-child(2)"); $.post("http://urltogetdatafromdatabase.com/getinfo.ph ...

What is the best way to modify or revise meta fields for individual products in order to retrieve multiple images for each product in Shopify?

What is the process for updating or editing meta fields to display multiple images of each product on Shopify? ...

The Static Interface Binding in TypeScript

I have inquired about how to extend the static functionality of existing objects in JavaScript (using TypeScript). In all examples provided here, I am utilizing Object The code below showcases a polyfill definition for ECMAScript's Object.is function ...

Is there a way to transform JSON text into a JSON object?

Similar Question: How do I convert JSON to a JavaScript object? { "data": [ { "name": "JoongBum Lee", "id": "526210623" }, { "name": "\uc774\uc778\uaddc", ...

Error in Angular Google Maps Component: Unable to access the 'nativeElement' property as it is undefined

I am currently working on creating an autofill input for AGM. Everything seems to be going smoothly, but I encountered an error when trying to integrate the component (app-agm-input) into my app.component.html: https://i.stack.imgur.com/mDtSA.png Here is ...

Is it feasible to update just one key within an exported type using setState in TypeScript?

Recently, I decided to dive into Typescript within my React App and encountered an error that has left me stumped. I'm wondering if it's possible to access a specific key in an exported type and modify its value? Here is how I've exported ...