Elegantly intersect two types of functions in Typescript

Two function types are defined as follows:

wrapPageElement?(
  args: WrapPageElementBrowserArgs<DataType, PageContext, LocationState>,
  options: PluginOptions
): React.ReactElement

.. and ..

wrapPageElement?(
  args: WrapPageElementNodeArgs<DataSet, PageContext>,
  options: PluginOptions
): React.ReactElement

Although the functions are almost identical, the only difference lies in the type of args. This variance is inconsequential for my particular use case. I can rely entirely on the intersecting type between these two functions. To address this, I have formulated the following type:

type Params1 = Parameters<GatsbyBrowser['wrapPageElement']>
type Params2 = Parameters<GatsbySSR['wrapPageElement']>
type Return1 = ReturnType<GatsbyBrowser['wrapPageElement']>
type Return2 = ReturnType<GatsbySSR['wrapPageElement']>
type WrapPageElement = (args: Params1[0] | Params2[0], options: Params1[1] | Params2[1]) => Return1 | Return2;

Here is a minimal reproduction. Although functional, I am contemplating if there exists a more elegant way to define the type WrapPageElement, or if the current approach is optimal.

Tl;dr

type PluginOptions = object;
type ReactElement = object;
type WrapPageElementBrowserArgs = {element: object, browserArgs: object};
type WrapPageElementNodeArgs = {element: object, nodeArgs: object};


type GatsbyBrowser = {
  wrapPageElement: (
    args: WrapPageElementBrowserArgs,
    options: PluginOptions
  ) => ReactElement
}

type GatsbySSR = {
  wrapPageElement: (
    args: WrapPageElementNodeArgs,
    options: PluginOptions
  ) => ReactElement
}

type Params1 = Parameters<GatsbyBrowser['wrapPageElement']>
type Params2 = Parameters<GatsbySSR['wrapPageElement']>
type Return1 = ReturnType<GatsbyBrowser['wrapPageElement']>
type Return2 = ReturnType<GatsbySSR['wrapPageElement']>
type WrapPageElement = (args: Params1[0] | Params2[0], options: Params1[1] | Params2[1]) => Return1 | Return2;

type WrapPageElement2 = GatsbyBrowser['wrapPageElement'] & GatsbySSR['wrapPageElement']; // not what I need

Answer â„–1

If you're in search of a method to transform two function types, denoted as T and U, into a single function type where each parameter and return type is the union of corresponding parameters and return types from T and U, then you need a transformation I'll refer to as UnionFunctions<T, U>. Contrary to "intersecting" functions, combining function types using an intersection in TypeScript results in a multi-call signature function rather than a merger of parameters and return types.


One potential solution could be:

type UnionTuples<T extends any[], U extends { [K in keyof T]: any }> =
  { [K in keyof T]: T[K] | U[K] }    

type UnionFunctions<T extends (...args: any[]) => any, U extends (...args: any) => any> =
  (...args: UnionTuples<Parameters<T>, Parameters<U>>) => ReturnType<T> | ReturnType<U>

In this scenario, UnionTuples<T, U> takes two tuples, T and U, and generates a unified tuple with elements representing unions of respective components from T and U. For instance,

UnionTuples<[1, 2], [3, 4]>
would yield [1 | 3, 2 | 4]. This process leverages a direct mapped tuple type.

Subsequently, UnionFunctions utilizes UnionTuples on the parameter list extracted from T and U (utilizing the Parameters<T> utility type) while unifying return types through the ReturnType<T> utility type.

Although defining UnionFunctions does not involve excessive verbosity, it may require more characters compared to manual iteration. Repeated use or extensive parameter lists are essential scenarios wherein adopting UnionFunctions becomes advantageous.


Applying this logic to your example:

type WrapPageElement = UnionFunctions<
  GatsbyBrowser['wrapPageElement'], 
  GatsbySSR['wrapPageElement']
>
/* type WrapPageElement = (
     args: WrapPageElementBrowserArgs | WrapPageElementNodeArgs, 
     options: PluginOptions
 ) => ReactElement */

The outcome appears satisfactory!

Access Playground code here

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

Achieving Jest integration with Angular 9 in a Storybook setup

We are currently utilizing Storybook 5 alongside Angular 9, with Jest 26 for some of the testing procedures. The issue we're facing arises when using Typescript version below 3.8.0 - a requirement for Angular 9's ng build --prod. This results in ...

Exploring the concept of object inheritance in Angular 5 with Typescript

I'm facing a challenge related to inheritance while building my initial angular 5 application. The error message I encounter is: Property 'message' does not exist on type 'CouponEvent', as reported by the angular-cli. export class ...

Setting a default value dynamically in a `select` control can be done by using JavaScript to

Upon subscribing to the HTTP server for data retrieval, my select control in Angular framework gets loaded with the received data. My objective is to set a default value that comprises three values from the server object separated by slashes ("/"), which r ...

Error in Ionic2 TypeScript: 'google' and '$' names not found, despite Google Maps and jQuery functioning correctly

I am currently working on developing an ionic2 application using TypeScript. Within the index.html file, I have attempted to integrate jquery and the Google Maps JS API before cordova.js: <!-- Vendor --> <script src="https://maps.googleapis. ...

Error: The version of @ionic-native/[email protected] is not compatible with its sibling packages' peerDependencies

When attempting ionic cordova build android --prod, the following error occurred: I have tried this multiple times. rm -rf node_modules/ rm -rf platforms/ rm -rf plugins/ I deleted package.lock.json and ran npm i, but no luck so far. Any ideas? Er ...

Creating a Typescript interface where one property is dependent on another property

Let's look at an illustration: type Colors = { light: 'EC3333' | 'E91515' dark: '#100F0F' | '140F0F' } interface Palette { colorType: keyof Colors color: Colors[keyof Colors] } Is it possible for the ...

Guide on troubleshooting *.ts files in an ionic 2 project using Chrome's inspect devices feature

After successfully creating my Ionic 2 application for Android using the command "ionic build android", everything seems to be working fine. I have been debugging the app by using Chrome inspect devices, but now I am facing an issue. I am trying to debug ...

Tips for refreshing the value of a dependency injection token

When using Angular dependency injection, you have the ability to inject a string, function, or object by using a token instead of a service class. To declare it in my module, I do this: providers: [{ provide: MyValueToken, useValue: 'my title value& ...

Reducing image file sizes in Ionic 3

I have been struggling to compress an image client-side using Ionic 3 for the past couple of days. I have experimented with: ng2-img-max - encountered an error when utilizing the blue-imp-canvas-to-blob canvas.toBlob() method (which is a dependency of ng2 ...

angular2 ngif does not effectively conceal HTML elements when set to false

In the HTML file, I have the following code: <p *ngIf="!checklistsready"> not ready </p> <p *ngIf="checklistsready"> Ready </p> And in my TypeScript file, it looks like this: checklistsready: boolean = false; constructor( ...

Establish a connection between MongoDB and the built-in API in Next.js

I've been working on integrating a MongoDB database with the Next.js built-in API by using the code snippet below, which I found online. /api/blogs/[slug].ts import type { NextApiRequest, NextApiResponse } from 'next' import { connectToData ...

Switch up the styling of a component by updating its properties with a switch statement

Although there is a similar question, my query has a unique requirement. I have defined the common styles for my button and implemented a function using a switch statement with different properties for various buttons across different pages. However, for ...

Hiding the keypad on an Android device in an Ionic app when user input is detected

I am currently utilizing the syncfusion ej2 Calendar plugin for a datepicker, but I am only using options such as selecting ranges like today, 1 month, or last 7 days from the plugin itself. The plugin provides dropdown options when the calendar is trigger ...

Leverage the Node Short ID library in conjunction with Angular 6 using TypeScript

I attempted to utilize the node module within an Angular 6 typescript environment. Step one: npm i shortid Within my TypeScript class: import { shortid } from 'shortid'; let Uid = shortid.generate(); However, I encountered an error stating ...

Incorporate a JavaScript script into an Angular 9 application

I have been experiencing issues trying to add a script.js file to angular.json and use it in one component. Adding a script tag directly to my HTML file is not the ideal solution. Can someone suggest an alternative approach or point out what I may be missi ...

I'm having trouble with implementing a basic show/hide feature for the login and logout options in the navigation bar using Angular. Can anyone help me figure out why it's

Is there a way to display the functionality after logging in without using session storage or implementing the logout function? The HTML for navigation is provided below. <nav class="navbar navbar-expand-sm navbar-light bg-light"> ...

Use bracket notation to verify if a property is undefined

Having some difficulty determining if the property value of an object is undefined when accessed dynamically with bracket notation. Here's a snippet of my code: function toBritishDate(date: Date | string): string { console.log(date) return &qu ...

Is there a way to verify the presence of data and halt code execution if it is not found?

In my data, there is a table containing a total of 5 links. The first 2 links can vary in availability as they are dynamic, while the last 3 links are static and are always displayed. The dynamic links' data is deeply nested within the state object, s ...

Is there a way for me to showcase a particular PDF file from an S3 bucket using a custom URL that corresponds to the object's name

Currently, I have a collection of PDFs stored on S3 and am in the process of developing an app that requires me to display these PDFs based on their object names. For instance, there is a PDF named "photosynthesis 1.pdf" located in the biology/ folder, and ...

The practice of following the UpperCamelCase convention post object transformation

I encountered a situation where I have an object that returned the result from an RxJs subscribe method: result: any { message: null role: Object success: true } To better manage this object in TypeScript, I decided to convert it to a type ca ...