Consolidate multiple generic items into a single entry

In my current project, I am structuring the types for a complex javascript module.
One of the requirements is to handle multiple types using generics, as shown in the snippet below:

export interface ModelState<
    FetchListPayload,
    FetchListResponse,
    FetchListError,
    FetchSinglePayload,
    FetchSingleResponse,
    FetchSingleError,
    CreatePayload,
    CreateResponse,
    CreateError,
    UpdatePayload,
    UpdateResponse,
    UpdateError,
    DeletePayload,
    DeleteResponse,
    DeleteError
> {
    fetchList: FetchListState<FetchListPayload, FetchListResponse, FetchListError>;
    fetchSingle: FetchSingleState<FetchSinglePayload, FetchSingleResponse, FetchSingleError>;
    create: CreateState<CreatePayload, CreateResponse, CreateError>;
    update: UpdateState<UpdatePayload, UpdateResponse, UpdateError>;
    del: DeleteState<DeletePayload, DeleteResponse, DeleteError>;
}

I am exploring options to define all the generic types at once, like this:

interface CreateModelTypes<
    FetchListPayload,
    FetchListResponse,
    FetchListError,
    FetchSinglePayload,
    FetchSingleResponse,
    FetchSingleError,
    CreatePayload,
    CreateResponse,
    CreateError,
    UpdatePayload,
    UpdateResponse,
    UpdateError,
    DeletePayload,
    DeleteResponse,
    DeleteError
> {
    FetchListPayload: FetchListPayload;
    FetchListResponse: FetchListResponse;
    FetchListError: FetchListError;
    FetchSinglePayload: FetchSinglePayload;
    FetchSingleResponse: FetchSingleResponse;
    FetchSingleError: FetchSingleError;
    CreatePayload: CreatePayload;
    CreateResponse: CreateResponse;
    CreateError: CreateError;
    UpdatePayload: UpdatePayload;
    UpdateResponse: UpdateResponse;
    UpdateError: UpdateError;
    DeletePayload: DeletePayload;
    DeleteResponse: DeleteResponse;
    DeleteError: DeleteError;
}

Subsequently, I aim to define ModelState in this way:

export interface ModelState<CreateModelTypes> {
    fetchList: FetchListState<CreateModelTypes.FetchListPayload, CreateModelTypes.FetchListResponse, CreateModelTypes.FetchListError>;
    fetchSingle: FetchSingleState<CreateModelTypes.FetchSinglePayload, CreateModelTypes.FetchSingleResponse, CreateModelTypes.FetchSingleError>;
    create: CreateState<CreateModelTypes.CreatePayload, CreateModelTypes.CreateResponse, CreateModelTypes.CreateError>;
    update: UpdateState<CreateModelTypes.UpdatePayload, CreateModelTypes.UpdateResponse, CreateModelTypes.UpdateError>;
    del: DeleteState<CreateModelTypes.DeletePayload, CreateModelTypes.DeleteResponse, CreateModelTypes.DeleteError>;
}

It seems that the syntax used above is incorrect. Is there any feature in TypeScript that can assist me with this issue?

Answer №1

To enhance readability, I suggest transforming CreateModelTypes into a specific type with predetermined keys, all having values of type unknown:

type CreateModelTypes = Record<
  | "FetchListPayload" | "FetchListResponse" | "FetchListError"
  | "FetchSinglePayload"  | "FetchSingleResponse"  | "FetchSingleError"
  | "CreatePayload" | "CreateResponse" | "CreateError"
  | "UpdatePayload" | "UpdateResponse" | "UpdateError"
  | "DeletePayload" | "DeleteResponse"  | "DeleteError",
  unknown
>;

Next, make ModelState generic in C, which is restricted to CreateModelTypes. In TypeScript, utilizing the indexed access type syntax T[K] for object types with key type K is preferred over T.K. This leads to the following implementation for ModelState:

interface ModelState<C extends CreateModelTypes> {
  fetchList: FetchListState<
    C["FetchListPayload"], C["FetchListResponse"], C["FetchListError"]
  >;
  fetchSingle: FetchSingleState<
    C["FetchSinglePayload"], C["FetchSingleResponse"], C["FetchSingleError"]
  >;
  create: CreateState<
    C["CreatePayload"], C["CreateResponse"], C["CreateError"]
  >;
  update: UpdateState<
    C["UpdatePayload"], C["UpdateResponse"], C["UpdateError"]
  >;
  del: DeleteState<
    C["DeletePayload"], C["DeleteResponse"], C["DeleteError"]
  >;
}

The definition for a specific generic type would then look like this:

type SomeModelState = ModelState<{
  FetchListPayload: string;
  FetchListResponse: number;
  FetchListError: Error;
  FetchSinglePayload: number;
  FetchSingleResponse: boolean;
  FetchSingleError: Error;
  CreatePayload: boolean;
  CreateResponse: object;
  CreateError: Error;
  UpdatePayload: object;
  UpdateResponse: RegExp;
  UpdateError: Error;
  DeletePayload: RegExp;
  DeleteResponse: string;
  DeleteError: Error;
}>;

A potential pattern from the entity names/types indicates that grouping should take place at two levels deep, as shown below:

interface State {
  Payload: unknown;
  Response: unknown;
  Error: unknown;
}

interface CreateModelTypes {
  FetchList: State;
  FetchSingle: State;
  Create: State;
  Update: State;
  Delete: State;
}

interface ModelState<C extends CreateModelTypes> {
  fetchList: FetchListState<
    C["FetchList"]["Payload"],
    C["FetchList"]["Response"],
    C["FetchList"]["Error"]
  >;
  fetchSingle: FetchSingleState<
    C["FetchSingle"]["Payload"],
    C["FetchSingle"]["Response"],
    C["FetchSingle"]["Error"]
  >;
  create: CreateState<
    C["Create"]["Payload"],
    C["Create"]["Response"],
    C["Create"]["Error"]
  >;
  update: UpdateState<
    C["Update"]["Payload"],
    C["Update"]["Response"],
    C["Update"]["Error"]
  >;
  del: DeleteState<
    C["Delete"]["Payload"],
    C["Delete"]["Response"],
    C["Delete"]["Error"]
  >;
}

type SomeModelState = ModelState<{
  FetchList: { Payload: string; Response: number; Error: Error };
  FetchSingle: { Payload: number; Response: boolean; Error: Error };
  Create: { Payload: boolean; Response: object; Error: Error };
  Update: { Payload: object; Response: RegExp; Error: Error };
  Delete: { Payload: RegExp; Response: string; Error: Error };
}>;

Both approaches are valid. Please note the lackluster IntelliSense support when defining C in SomeModelState. It does not provide key hints despite being necessary for compilation. If needed, consider raising an issue regarding this matter.


I hope this guidance proves helpful. Best of luck with your implementation!

Link to code

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

Having trouble selecting an element by name that contains a specific string followed by [..] using jQuery

I have elements with names like kra[0][category], kra[1][category], and so on. However, I am facing difficulties in selecting these elements by name. Here is my jQuery code: $("[name=kra[0][category]]").prop("disabled", true); ...

What is the reason that when a <div> click on a mobile device includes a jQuery ('#item').fadeOut() function, the CSS of the '#item' element maintains its previous hover state

While creating a mock back to top button, I encountered an issue with my jQuery call to fadeOut() behaving differently on mobile devices (this discrepancy can be observed using any desktop browser simulator). You can find the source code here: https://cod ...

Activating the Play button to start streaming a link

Recently delved into the world of Ionic 4 and Angular, so definitely a beginner :) Purchased a UI Template from code canyon but didn't realize I needed to code the music player part. Been trying to get a music stream playing but no luck. Came across ...

Rendering with Next.js script

Within my Next.js project, there is a script implemented to render a widget. The code for this script looks like: <a className="e-widget no-button xdga generic-loader" href="https://example" rel="no ...

Using window.open in Google Chrome results in multiple tabs being opened instead of just one

I have a dynamic input field with a search button. Initially, only one tab is opened when I click the button. But after saving and clicking it again, multiple tabs open for the same URL. The number of tabs is equal to the number of dynamic input fields cre ...

Efficiently refresh several DIV elements with a single JSON request

After extensive searching, I've come to the conclusion that due to my limited proficiency in javascript, I need some help. The issue at hand is with a json format output produced by an ASP page on a separate webserver, which looks something like this: ...

Angular 14 debug error: Incorrect base path setting

Whenever I go for a run, I have to specify a starting point such as /pis/ ng serve --serve-path /pis/ Even after following these instructions, nothing seems to load. Can anyone lend a hand with setting a starting point in the ng serve process? ...

Sending form data after a successful AJAX request with jQuery in ASP.NET MVC 5

Within my Asp.net MVC 5 project, there exists a form equipped with a Submit button. Upon clicking this Submit button, the following tasks are to be executed: Client-side validation must occur using jQuery on various fields (ensuring required fields are ...

Issue: $controller:ctrlreg The controller named 'HeaderCntrl' has not been properly registered

I am encountering an error while working on my AngularJS program. I have defined the controller in a separate file but it keeps saying that the controller is not registered. Can someone please help me understand why this issue is happening? <html n ...

Adding optional properties to TypeScript interfaces

As discussed in this post, the optional ? operator is commonly used to indicate that a function parameter can be omitted. But what is the significance of the ? operator when it appears on interface parameters? For instance, consider the following TypeScrip ...

Avoid losing focus on href="#" (preventing the page from scrolling back up to the top)

Is there a way to prevent an empty href link from causing the page to scroll up when clicked? For example, if I have: <a href="#">Hello world</a> And this "Hello world" link is in the middle of the page. When clicked, the URL would look like ...

Having trouble with Angular's ng-tags-input? Are you getting the error message "angular is not defined"? Let

I recently created a new Angular project using the following command: ng new "app-name" Now, I'm attempting to incorporate ngTagsInput for a tag input feature. However, every time I try to build and run my app, I encounter an error in my browser con ...

Utilizing Data Filters to Embed HTML in Vue.js?

I have been attempting to utilize the Filter feature in Vue.js to insert HTML tags within a String. The documentation indicates that this should be achievable, however, I am not having any success. The objective is for the data to be just a String that is ...

Discover how to initiate an ajax request from a tailored module when the page is loaded in ActiveCollab

When trying to initiate an AJAX call on the project brief page by adding a JavaScript file, I encountered some issues. My goal is to display additional information along with the existing project brief. I included a JavaScript file in a custom module and f ...

When trying to insert a string variable using Node and mysql, the operation fails

My attempt to add a string variable into the database is causing an issue. The boolean and integer values insert without any problems, but I keep receiving the following error message: Error: column "spike" does not exist (The value "spike" corresponds ...

Using global variables in NodeJS MySQL is not supported

Why am I unable to access the upcoming_matches array inside the MySQL query callback? Whenever I try to log upcoming_matches[1], I get a 'TypeError: Cannot read property '1' of null' error message in the console. This indicates that th ...

Display new information within a div element seamlessly without refreshing the page

Just a heads-up, I'm new to HTML development... Currently working on a website using Bootstrap. There are some buttons on the left side shown in the screenshot, and my goal is to update the content on the right without having to reload the entire pag ...

Locate a specific option that matches the value of the selected data-status and set it as "selected" using jQuery

Currently, I am facing an issue where I need to load 2 separate ajax responses into a select dropdown. The select dropdown will have a data-status attribute, and my goal is to loop through the options to find the one that matches the value of the select da ...

What is the process for accessing the data attributes of div elements and then transferring them to a cookie?

Although this question may have been answered before, I couldn't find a specific solution. Imagine I have the following div elements... <div class="listings-area"> <div itemtype="http://schema.org/Product" itemscope=""> <a class="lis ...

React Issue: Make sure to assign a unique "key" prop to each child element within a mapped list

I encountered the error message below: react Each child in a list should have a unique "key" prop. Here is my parent component code snippet: {data.products .slice(4, 9) .map( ({ onSale, ...