Implement type constraints on a parameter and ensure the return value maintains the original parameter type

In my code, there is a function called createModule that simply returns its parameter.

function createModule(obj) {
  return obj
}

The returned value must be of the same type as the parameter provided:

interface Mod1State {
  p1: string
}

const mod1 = createModule({
  namespaced: true,
  state: {
    p1: "abc"
  } as Mod1State,
  mutations: {
    SET_P1(state, p1: string) {
      state.p1 = p1
    }
  }
} as const)

// The expected type of 'mod1' should be: '{ namespaced: true, state: Mod1State, mutations: { SET_P1(state: any, p1: string): void } }'

Initially, achieving this requirement seems straightforward:

function createModule<T>(obj: T): T {
  return obj
}

Now, I want to introduce auto-completion for the state parameter within the SET_P1 function and prefer checking the state property instead of using casting.

    SET_P1(state, p1: string) {
      // Here, 'state' should be of type Mod1State
    }

This was my attempt:

function createModule<S, T extends WithState<S> = WithState<S>>(obj: VuexModule<T, S>): T {
  return obj
}

interface WithState<S> {
  state?: S
}

type VuexModule<T extends WithState<S>, S = T["state"]> = T & {
  namespaced?: boolean
  state?: S
  mutations?: {
    [K: string]: (state: S, payload: any) => void
  }
}

However, it only works if I remove the as const part (the reason behind this eludes me):

const mod1 = createModule<Mod1State>({
  namespaced: true,
  state: { // This checks the type of 'state'
    p1: "abc"
  },
  mutations: {
    SET_P1(state, p1: string) { // Ensures 'state' has the 'Mod1State' type
      state.p1 = p1
    }
  }
})

But now, the type of mod1 becomes WithState<Mod1State>. The exact inferred type is lost. How can I restore the precise type of the parameter passed to createModule to be reflected in the return value?

For further reference, see the playground example in the playground.

EDIT: I managed to get something working from a modified version of my original code. The functioning nature of it perplexes me, especially how the namespaced type is deduced as true without the need for as const, instead of being recognized as a boolean.

Answer №1

Due to the inability to replicate the intense inferred typing of the state parameter in the mutations callback, I will disregard this and suggest adding annotations manually if you want the compiler to recognize it.

In the upcoming explanation, I will simplify VuexModule by focusing solely on S, representing the state property type:

type VuexModule<S> = {
  state?: S,
  namespaced?: boolean
  mutations?: {
    [K: string]: (state: S, payload: any) => void
  }
}

Let's dive into it:


If you wish to define Mod1State as the S type parameter but allow the compiler to infer the T type parameter based on both S and the value passed into createModule(), then partial type parameter inference is required, which is not yet supported as of TS3.7. The two known workarounds are using a curried function or passing a dummy parameter of type

S</code, each with its own set of challenges:</p>

<pre><code>// Curried function approach
const createModule = <S>() => <T extends VuexModule<S>>(t: T) => t;

// Dummy parameter workaround
const createModule = <S, T extends VuexModule<S>>(s: S, t: T) => t;

Both methods yield strong typing for mod1 but are somewhat cumbersome to use effectively.


On the other hand, if you prefer the compiler to infer both S and T, there is a way to achieve that through constraining T to a function of itself in what's known as F-bounded quantification:

const createModule = <T extends VuexModule<T["state"]>>(t: T): T => t;

This method ensures that T complies with the inferred type of

S</code, providing robust typing for <code>mod1
.

I hope this clarifies matters. Good luck!

Answer №2

Sharing a solution that appears to be effective. The reasoning behind it is not entirely clear.

interface Mod1State {
  p1: string
}

function createModule<
  T extends WithState,
  S = T["state"]
>(obj: T & VuexModule<S>): T {
  return obj
}

interface WithState {
  state?: any | (() => any)
}

interface VuexModule<S> {
  namespaced?: boolean
  state?: S | (() => S)
  mutations?: {
    [K: string]: (state: S, payload: any) => void
  }
}

const mod1 = createModule({
  namespaced: true,
  state: {
    p1: "abc"
  } as Mod1State,
  mutations: {
    SET_P1(state, p1: string) { // Good: 'state' is of type 'Mod1State'
      state.p1 = p1
    }
  }
})
// Good: 'mod1' has the correct type,
// including 'namespaced: true' instead of 'namespaced: boolean'

Playground

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

What is the contrast between element.getAttribute() value and a String in protractor?

When using protractor and typescript, I need to verify that the text saved in a textbox matches a certain string by comparing it with the resulting value of element.getAttribute("value"). Unfortunately, getText() does not work for this scenario b ...

When utilizing Angular, the mat-datepicker is displayed underneath the modal, even after attempting to modify the z-index

I am encountering a problem with a mat-datepicker displaying below a modal in my Angular application. Here are the key details: Html: <div class="col-12"> <mat-form-field appearance="fill"> <mat-label>Start Date ...

(firebase-admin) Issue: Why is the client showing as offline when it's actually online?

import * as admin from "firebase-admin"; import DataModel from "../types/firebase"; export class FirebaseManager { db = admin.database(); constructor() { this.db = admin.database(); if (this.db === undefined) { throw ...

In TypeScript and React, what is the appropriate type to retrieve the ID of a div when clicked?

I am facing an issue in finding the appropriate type for the onClick event that will help me retrieve the id of the clicked div. const getColor = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const color = event.target.id; // ...

The specified reference token grant value of [object Object] could not be located in the store

Currently, I am working with NestJs along with the oidc passport strategy using identityserver. Below is a snippet of the code: import { UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; ...

When I utilize the redux connect function, the class information in my IDE (PhpStorm/WebStorm) seems to disappear

When I use the connect function from redux, it seems to hinder my IDE (PhpStorm) from "Find Usages" on my classes. This is likely because connect returns any, causing the type information from the imported SomeClass file to be lost. export default connect ...

Monitoring URL changes in Angular2 using the HostListener

I have a common navbar component that is included in every page of my website. I would like it to detect when the URL changes using a HostListener. @HostListener('window:hashchange', ['$event']) onHashChange(event) { this.checkCu ...

Issue with the physical back button on Android devices

Whenever I press the physical Android Button I encounter the following error: While executing, an unhandled exception java.lang.IndexOutOfBoundsException: setSpan (2..2) goes beyond length 0 Here is my app.routing.ts import { LoginComponent } from " ...

Is it possible to specify the data type of form control values when using the Angular Reactive form builder?

Is it possible to use typed reactive forms with Angular form builder? I want to set the TValue on the form control to ensure we have the correct type. For example: public myForm= this.fb.group({ name: ['', [Validators.required, Validators.max ...

Creating a function within a module that takes in a relative file path in NodeJs

Currently, I am working on creating a function similar to NodeJS require. With this function, you can call require("./your-file") and the file ./your-file will be understood as a sibling of the calling module, eliminating the need to specify the full path. ...

Enhancing React TypeScript: Accurate typings for Route's location and children attributes

I am facing an issue with my router as it passes props of location and children, but I am uncertain about the correct types for these props. Here is the code snippet for the router using react-router-dom... import React, { useReducer } from 'react&a ...

Is there a way for me to retrieve the username of an object from a select list?

I am working with a select list that contains names, and I need to extract the name instead of the ID in order to insert it into the database. Below is my TypeScript file: selectUser() { this.UtilisateurService.findAll().then((res) => { let ...

When employing TypeScript, an error pops up stating "cannot find name 'ObjectConstructor'" when attempting to use Object.assign

I am rephrasing my query as I realized it was unclear earlier. I have an API that is sending me data in the following format: {"photos":[{"id":1,"title":"photo_1_title"}]} In my code, I have a variable called photos and a function named getPhotos() For ...

Navigating through object keys in YupTrying to iterate through the keys of an

Looking for the best approach to iterate through dynamically created forms using Yup? In my application, users can add an infinite number of small forms that only ask for a client's name (required), surname, and age. I have used Formik to create them ...

Display the number of objects in an array using Angular and render it on HTML

I am having trouble displaying the length of an array on my HTML page. No errors are showing up in the console either. Can someone help me figure out how to get the total number of heroes? HTML: <div *ngFor="let hero of heros"> <div>The tota ...

Obtain characteristics of the primary element in Ionic version 2 or 3

After creating and opening my Sqlite database and saving the SQLiteObject in a variable within my app.component.ts, I now need to retrieve these attributes in my custom ORM. The ORM extends to other providers to describe the title and field of my tables. ...

Tips for patiently awaiting the completion of a fadeout function

After a fadeOut function completes, I want to update a value. Here is the function I am using: const fadeOut = (duration: number = 300) => { Animated.timing( opacity, { toValue: 0, dura ...

Inserting item into an array

Currently, I am storing an array in a Firestore document and I am trying to add another object to it but facing some issues. For instance, value:m1 , status:open The code snippet below is from home.html, where you can find [(ngModel)]="words2[in].value" ...

Can you explain why Argument of type 'Element' cannot be assigned to a parameter of type 'never'?

The Chessboard Challenge export default function GenerateChessboard(){ let chessboard = []; for(let i = 0 ; i < rowsArray.length; i++) { for(let j = columnsArray.length-1 ; j >= 0 ; j--) { const number = i + j ...

Error: The body is not usable - POST action from NextJS server

In my project with NextJS v14.1.0, I encountered an issue while using server action in a client component. The error message is showing correctly, but I also receive a TypeError stating that the body is unusable. src/app/auth/account-verification/page.tsx ...