What is the best way to implement a dispatch function in TypeScript?

Despite my expectations, this code does not pass typechecking. Is there a way to ensure it is well typed in Typescript?

const hh = {
  a: (_: { type: 'a' }) => '',
  b: (_: { type: 'b' }) => '',
} as const;

export function dispatch<
  Arg extends Parameters<(typeof hh)[keyof typeof hh]>[0],
>(arg: Arg) {
  const h = hh[arg.type];
  h(arg);
}

The purpose of the dispatch() function is to accept a valid argument for one of the functions stored in hh and invoke that function with the given argument.

Answer №1

The compiler is having difficulty handling the concept of "correlated union types" as discussed in ms/TS#30581. However, there is a suggested workaround provided officially in ms/TS#47109, which involves using a map type that acts as the single source of truth for functions and dispatch arguments.

Let's define this map type:

type ActionMap = {
  a: {
    payload?: {};
  };
  b: {
    payload?: {};
  };
};

Assuming that you are working with redux actions, I have incorporated an optional payload property.

Now, we can type hh utilizing this type along with mapped types:

const hh: {
  [K in keyof ActionMap]: (action: ActionMap[K] & { type: K }) => string;
} = {
  a: (action) => '',
  b: (action) => '',
};

We will also create another type for the dispatch arguments, where we return a default union along with specific arguments based on the generic argument representing the dispatched action:

type DispatchArgs<T extends keyof ActionMap = keyof ActionMap> = {
  [K in T]: { type: K } & ActionMap[K];
}[T];

Example of usage:

export function dispatch<ActionType extends keyof ActionMap>(
  arg: DispatchArgs<ActionType>,
) {
  const h = hh[arg.type];

  h(arg); // no error
}

Link to Playground

Answer №2

TLDR: https://tsplay.dev/we8MYW

To simplify the implementation of the map, I created a function that generates a dispatch function tailored for a specific key in the map. This approach allowed us to validate the key against the set rules before proceeding.

function createDispatch(hh: {[key in string]: (_: { type: key }) => any}) {
  return dispatch(arg: { type: key }) {
    // ...
  }
}

I then created a Generic type that takes a union of keys and outputs an object where keys and values are identical.

type ExactValue<Of extends string> = {
  [K in Of]: K;
};

type TestExactValue = ExactValue<"a" | "b"> // { a: "a", b: "b" }
type HH<O> = { readonly [key in keyof O]: (_: { type: O[key]}) => any }

type TestHH = HH<TestExactValue> // { a: (_: { type: "a" }) => any, b: (_: { type: "b" }) => any }
function createDispatcher<Key extends keyof typeof hh, O extends ExactValue<Key>, WW extends { readonly [key in keyof O]: (_: { type: O[key]}) => any }>(hh: WW) {
type ExactValue<Of extends string> = {
  [K in Of]: K;
};


function createDispatcher<Key extends keyof typeof hh, O extends ExactValue<Key>, WW extends { readonly [key in keyof O]: (_: { type: O[key]}) => any }>(hh: WW) {
  return function dispatch<MyKey extends Key>(arg: { type: Key } & { type: MyKey }) {
    const h = hh[arg.type]
    return h(arg as any) as ReturnType<typeof h>
  }
}

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

How to arrange data in angular/typescript in either ascending or descending order based on object key

Hey there! I'm fairly new to Angular and have been working on developing a COVID-19 app using Angular. This app consists of two main components - the State component and the District component. The State component displays a table listing all states, ...

The 'current' in react typescript is not found within the type 'never'

Currently, I am working with react and typescript in my project. To fetch the height of a specific div tag, I decided to utilize useRef method. However, when trying to access 'current' property, TypeScript throws an error. Property 'current& ...

The functionality of TypeScript's .entries() method is not available for the type 'DOMTokenList'

I'm currently working with a stack that includes Vue3, Vite, and TypeScript. I've encountered an issue related to DOMTokenList where I'm trying to utilize the .entries() method but TypeScript throws an error saying Property 'entries&apo ...

Encountering 'null' error in template with Angular 4.1.0 and strictNullChecks mode

After updating Angular to version 4.1.0 and activating "strictNullChecks" in my project, I am encountering numerous errors in the templates (.html) that look like this: An object may be 'null' All these errors are pointing to .html templat ...

The data type 'T[K]' does not meet the required conditions of 'string | number | symbol'

I am currently in the process of developing a straightforward function to eliminate duplicates from an array using TypeScript. While I acknowledge that there are numerous methods to accomplish this task, my main objective is to enhance my understanding of ...

The TypeScript error message indicates that the property 'forEach' is not found on the 'FileList' type

Users are able to upload multiple files on my platform. After uploading, I need to go through each of these files and execute certain actions. I recently attempted to enhance the functionality of FileList, but TypeScript was not recognizing the forEach m ...

Error: Unable to initialize i18next as a function

For my current project, I am utilizing TypeScript and i18next for internalization. TypeScript version: 2.1.4 i18next version: 2.3.4 @types/i18next version: 2.3.35 In the specific file: import * as i18next from 'i18next'; i18next.init({ ...

Is there a way to append a unique property with varying values to an array of objects in TypeScript?

For instance: items: object[] = [ {name: 'Backpack', weight: 2.5}, {name: 'Flashlight', weight: 0.8}, {name: 'Map', weight: 0.3} ]; I prefer the items to have an 'age' property as well: it ...

Comparing dates in Angular 6 can be done by using a simple

Just starting with angular 6, I have a task of comparing two date inputs and finding the greatest one. input 1 : 2018-12-29T00:00:00 input 2 : Mon Dec 31 2018 00:00:00 GMT+0530 (India Standard Time) The input 1 is retrieved from MSSQL database and the in ...

Tips for parsing through extensive JSON documents containing diverse data types

In the process of developing an npm package that reads json files and validates their content against predefined json-schemas, I encountered issues when handling larger file sizes (50MB+). When attempting to parse these large files, I faced memory allocati ...

Required Ionic form field alert

Currently, I am developing a new app using ionic 3 and I am facing an issue with making inputs mandatory in my ionic-alert controller. Despite going through the ionic-component documentation and api documentation, I couldn't find a solution on how to ...

Converting an Array of Objects into a single Object in React: A Tutorial

AccessData fetching information from the database using graphql { id: '', name: '', regions: [ { id: '', name: '', districts: [ { id: '', ...

Looking for a button that can be toggled on and off depending on the input fields

Even after adding useEffect to my code, the button component remains disabled unless the input fields are filled. It never enables even after that. export default function Page() { const [newPassword, setNewPassword] = useState(''); const [conf ...

What is the best way to display data retrieved through an HTTP service using ngFor?

I was attempting to inject a service (contact.service.ts) into a component (contact-list.component). The service contains data on employees defined in contacts.ts. While I was able to successfully receive the data, I encountered difficulty in rendering it ...

Determine to which observable in the list the error corresponds

const obs1$ = this.service.getAllItems(); const obs2$ = this.service.getItemById(1); combineLatest([obs1$, obs2$]) .subscribe(pair => { const items = pair[0]; const item = pair[1]; // perform actions }, err => { // det ...

Encountering the "Argument of type 'string' is not assignable to parameter of type 'never'" error when using Array.prototype.includes

The data type for keys is a combination of string[] | number[], which is derived from the ID type. The data type for id is simply ID. We want to check if the id exists within the array of keys. import React, { useState } from 'react'; type Distr ...

Utilize Lodash to iterate through functions in a loop and retrieve the first matching result

I am looking to iterate through an array of objects and call a method on them. If the result of that method meets certain conditions, I want to immediately return that result. The current implementation is as follows: public getFirstMatch(value: string, a ...

What could be the reason behind tsx excluding attribute names with "-" in them from being checked?

Why doesn't TypeScript check attributes with a hyphen in the name? declare namespace JSX { interface ElementAttributesProperty { props:any; // specify the property name to use } } class A{ props!:{p1:string} } const tsx = <A p1="&q ...

How can I showcase array elements using checkboxes in an Ionic framework?

Having a simple issue where I am fetching data from firebase into an array list and need to display it with checkboxes. Can you assist me in this? The 'tasks' array fetched from firebase is available, just looking to show it within checkboxes. Th ...

The problem with the Custom Select Component: Error Arises When Utilizing the Generic Type <T,> with a Comma as Opposed to <T> Without a Comma

I recently developed a unique custom select component that extends the standard HTML select element. During the process, I made use of a generic type to accommodate a wide range of data types, but encountered an unexpected error. The issue seems to persist ...