Injecting properties into higher order functions in Typescript allows for the dynamic customization

I'm curious about the process of composing functions that take an object as the sole argument, where each higher order function adds a property. For instance, I have a function that takes a context as input. I would like to wrap this function with a withCookies function and then a withToken function, allowing me to access both cookies and token within the final function.


withCookies(
  withToken(
    (ctx) => {
      console.log(ctx.cookies) // Record<string, string>
      console.log(ctx.token) // string
    }
  )
)

What might be the typescript signature for such a higher order function?

Answer №1

Creating call signatures for the functions withCookies(), withToken(), and a combined function withProps() is certainly possible. However, one challenge you may face is related to type inference in TypeScript. The compiler might struggle with contextually typing the ctx parameter within your innermost function due to the generic nature of the functions involved. This can lead to difficulties in proper type parameter inference, as demonstrated in issues such as microsoft/TypeScript#33042 and microsoft/TypeScript#38872. To overcome this, manual specification of types at certain points in your code may be necessary for the compiler to correctly understand the logic.


To start, defining the interfaces Cookies and Token helps give clarity to the objects being referenced:

interface Cookies {
  cookies: Record<string, string>;
}
interface Token {
  token: string;
}

The withCookies() function should be generic with an object type T, taking a callback function of type (ctx: T & Cookies) => void and returning a function of type (ctx: T) => void:

/* 
declare const withCookies: (
  cb: (ctx: T & Cookies) => void
) => (ctx: T) => void 
*/

Similarly, withToken() follows a comparable structure but substitutes Cookies with Token:

/* 
declare const withToken: (
  cb: (ctx: T & Token) => void
) => (ctx: T) => void 
*/

The similarity in signatures suggests the possibility of a utility function like withProps() that generates these functions. Here's a sample implementation:

const withProps = <U extends object>(u: U) =>
  <T extends object>(cb: (ctx: T & U) => void) =>
    (ctx: T) => cb({ ...ctx, ...u });

By using withProps() with Cookies as a parameter, you create the withCookies function:

const withCookies = withProps<Cookies>(
  { cookies: { a: "hello", b: "goodbye" } }
);

A similar approach applies to generate withToken by calling withProps() with Token:

const withToken = withProps<Token>(
  { token: "token" }
);

You can validate that these functions align with the defined call signatures.


Attempting to use these functions uncovers contextual typing challenges:

const badResult = withCookies(
  withToken(
    (ctx) => {
      console.log(ctx.cookies) // error! Property 'cookies' does not exist on type 'never'
      console.log(ctx.token) // error! Property 'token' does not exist on type 'never'
    }
  )
);
// const badResult: (ctx: never) => void

The inferred types result in errors due to incorrect contextually typed parameters. To address this, explicit annotations or specifying generic type parameters can be used, ensuring correct behavior:

const result = withCookies(
  withToken(
    (ctx: Cookies & Token) => {
      console.log(ctx.cookies)
      console.log(ctx.token)
    }
);
// const result: (ctx: object) => void

const alsoResult = withCookies(
  withToken<Cookies>(
    (ctx) => {
      console.log(ctx.cookies)
      console.log(ctx.token)
    }
  )
);
// const alsoResult: (ctx: object) => void

Both approaches lead to functional output, demonstrating the successful propagation of objects passed into withProps() into subsequent callbacks.


Click here to access the code via Playground link

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

Angular - failure to detect function execution by spy

I've been trying to test a feature module, but I'm facing some difficulties. The last test is failing because the spy isn't detecting that the method is being called, even when I move the this.translate.use(this.currentLanguage.i18n) call ou ...

The type 'number | { percent: number; }' cannot be assigned to the type 'string | number | undefined' according to ts(2322) error message

Presently, I am engaged in a project utilizing React and Typescript, where I am working on implementing a progress bar using react semantic-ui. I have encountered a typescript error in my code. Here is the segment causing the issue: import React, { Compo ...

Challenges faced when using an array of objects interface in Typescript

I have initialized an array named state in my component's componentDidMount lifecycle hook as shown below: state{ array:[{a:0,b:0},{a:1,b:1},{a:2,b:2}] } However, whenever I try to access it, I encounter the following error message: Prop ...

Gather the names of all properties from the filtered objects that meet specific criteria

Here is an example of an array: [ { "id": 82, "name": "fromcreate_date", "displayName": "From Create Date", "uiControl": "DATERANGE", }, { "id": 82, "name": "tocreate_date", "displayName": "To Create Date", "uiControl ...

Personalized data type for the Request interface in express.js

I'm attempting to modify the type of query in Express.js Request namespace. I have already tried using a custom attribute, but it seems that this approach does not work if the attribute is already declared in @types (it only works for new attributes a ...

Ways to transfer information from HTML form components to the Angular class

I am currently working with angular 9 and I have a requirement to connect data entered in HTML forms to the corresponding fields in my Angular class. Below is the structure of my Angular class: export interface MyData { field1: string, textArea1 ...

Issue: Module './App' not found in webpackSolution: Check if the module path is

I've decided to switch my .js files to .tsx in order to start using TypeScript. To incorporate TypeScript, I used the following command: yarn add typescript @types/node @types/react @types/react-dom @types/jest and began converting first index.tsx fo ...

Choose from the Angular enum options

I am working with an enum called LogLevel that looks like this: export enum LogLevel { DEBUG = 'DEBUG', INFO = 'INFO', WARNING = 'WARNING', ERROR = 'ERROR' } My goal is to create a dropdown select el ...

Issue encountered when trying to bring in a component from a different module

I am attempting to import the 'OpenDialogContentComponent' component from Module A into Module B, however I am encountering this error: 'Cannot determine the module for class OpenDialogContentComponent in C:/Users/jitdagar/Desktop/TDP/pwt-u ...

Are the functions 'useEffect' and 'useCallback' being repetitively used in this case?

I have implemented a custom hook in my React application to manage back navigation actions (POP). The functionality is operational, but I can't help but notice that useCallback and useEffect seem to be performing similar tasks. Here is the snippet of ...

Typescript: Creating an interface for a nested object "with a required key"

The objective is to prevent developers from declaring or accessing fields that the object does not possess. This also involves accurately defining a deeply nested object or schema. const theme: iTheme = { palletes: { primary: { main: "white", ...

I am interested in monitoring for any alterations to the @input Object

I've been attempting to detect any changes on the 'draft' Object within the parent component, however ngOnChange() does not seem to be triggering. I have included my code below, but it doesn't even reach the debugger: @Input() draft: ...

Using Angular, Typescript, and ngxs to manage state observables, one may wonder what exactly a variable ending with an exclamation mark (!) signifies. An example of this can be seen in the following code snippet:

Within my TS file, a declaration is present: import { Select } from '@ngxs/store'; @Injectable() export class someService { @Select(someSELECTOR) varName$!: Observable<someType[]>; elements$ = this.varName$.pipe( map(elements => e ...

Is that possible to prevent the use of the & symbol in Angular 4 HTTP requests?

Using an HTTP request to save data in my database. The code snippet I used is: const form = '&inputdata=' + myinput + '&rf_date=' + rf_date; return this.http.post(this.rootUrl, form, {headers : this.reqHeader}); In thi ...

Challenge Encountered with Create-React-App TypeScript Template: Generating JS Files Instead of TSX Files

Encountering a problem setting up a new React application with TypeScript using the Create-React-App template. Followed the guidelines on the official documentation (https://create-react-app.dev/docs/adding-typescript/) and ran the command below: npx creat ...

Configuring the parameters property for a SSM Association in AWS CDK

I am working on utilizing the AWS Systems Manager State Manager to automate shutting down an RDS instance at 9PM using a cron job. Currently, I am constructing the CloudFormation template with the help of AWS CDK. While going through the AWS CDK documenta ...

The variable 'selectedvalue' is being accessed before it has been initialized

I'm currently working on sharing the date between components using BehaviorSubject, but I'm encountering an error in the process. public data = new BehaviorSubject<any>(this.selectedValue); public sharedData = this.data.asObservable(); sele ...

Styling of Bootstrap HTML element not appearing as expected

Recently, I've been trying out a new approach by embedding Bootstrap components in an iframe. However, despite loading the necessary stylesheet and scripts, the elements seem to be missing their styles. Can anyone shed some light on why this might be ...

Having trouble getting web components registered when testing Lit Element (lit-element) with @web/test-runner and @open-wc/testing-helpers?

Currently, I am working with Lit Element and Typescript for my project. Here are the dependencies for my tests: "@esm-bundle/chai": "^4.3.4-fix.0", "@open-wc/chai-dom-equals": "^0.12.36", "@open-wc/testing-help ...

What is the best way to implement type discrimination in TypeScript?

When using TypeScript with two interfaces combined as a union type, why doesn't it distinguish which type members can be declared? interface IFish { swim: () => void; } interface ICat { meow: () => void; } type Pet = IFish | ICat; const p ...