How can you eliminate the prop that is injected by a Higher Order Component (HOC) from the interface of the component it produces

In my attempt to create a Higher Order Component, I am working on injecting a function from the current context into a prop in the wrapped component while still maintaining the interfaces of Props.

Here is how I wrap it:

interface Props extends AsyncRequestHandlerProps {
  bla: string;
}

class MyComponent extends React.Component<Props> {
 // ....
}

export default withAsyncRequestHandler(MyComponent)

This is how I have defined the withAsyncRequestHandler:

export interface AsyncRequestHandlerProps {
  asyncRequestHandler: <T>(promise: Promise<T>) => Promise<T | null>;
}

type PropsWithoutInjectedHandler<P> = Omit<P, keyof AsyncRequestHandlerProps>;


export function withAsyncRequestHandler<P>(Component: React.ComponentType<P>) {
  return class ComponentWithAsyncRequestHandler extends React.Component<
    PropsWithoutInjectedHandler<P>
  > {
    static contextType = AsyncHandlerContext;
    context!: AsyncHandlerContext | null;
    render = () => {
      const asyncRequestHandler: <T>(
        promise: Promise<T>
      ) => Promise<T | null> = (promise) => {
        if (this.context === null) {
          throw new Error(
            "withAsyncRequestHandler should only wrap components that are mounted inside <AsyncHandler />."
          );
        }
        return AsyncRequest(promise, this.context);
      };
      const { ...props } = this.props;
      return (
        <Component
          {...props}
          asyncRequestHandler={asyncRequestHandler}
        ></Component>
      );
    };
  };
}

The initial signature of MyComponent includes both the bla prop and the asyncRequestHandler props. My goal is for the wrapper HOC to return a component signature with just the bla prop after injecting the asyncRequestHandler.

Externally, the interface of this HOC appears to work fine as I can still access the remaining props from TypeScript when mounting the wrapped components.

However, internally within the HOC, I encounter an error:

When I mount the <Component> in render(), my current code generates this error:

Type 'Readonly<Pick<P, Exclude<keyof P, "asyncRequestHandler">>> & { asyncRequestHandler: <T>(promise: Promise<T>) => Promise<T | null>; children?: ReactNode; }' is not assignable to type 'IntrinsicAttributes & P & { children?: ReactNode; }'.
  Type 'Readonly<Pick<P, Exclude<keyof P, "asyncRequestHandler">>> & { asyncRequestHandler: <T>(promise: Promise<T>) => Promise<T | null>; children?: ReactNode; }' is not assignable to type 'P'.
    'P' could be instantiated with an arbitrary type which could be unrelated to 'Readonly<Pick<P, Exclude<keyof P, "asyncRequestHandler">>> & { asyncRequestHandler: <T>(promise: Promise<T>) => Promise<T | null>; children?: ReactNode; }'.ts(2322)

I suspect the issue lies around the construction of

Omit<P, keyof AsyncRequestHandlerProps>
and its usage?

Answer №1

As mentioned in a comment on this GitHub thread, it seems that there is a bug in TypeScript causing this issue.

After version 3.2, the behavior of the spread operator for generics has been altered. It seems that the type of props may be erased unintentionally. However, you can solve this by casting it back to P using {...props as P} when spreading back into the wrapped component.

To address this problem, you can try the following approach:

<Component
      {...props as P}
      asyncRequestHandler={asyncRequestHandler}
/>

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

A step-by-step guide for setting up SSL on a React Frontend, Nodejs Backend, and Custom Domain using Heroku

Important details about my current setup I am currently in the process of building a web application using React and a NodeJS API that supplies data for this web application. These two components are hosted separately on heroku.com. Additionally, I have o ...

How can I assign a type to an array object by utilizing both the 'Pick' and '&' keywords simultaneously in TypeScript?

As a beginner in TypeScript, I am looking to declare a type for an array object similar to the example below. const temp = [{ id: 0, // number follower_id: 0, // number followee_id: 0, // number created_at: '', // string delete_at: &ap ...

Tips on customizing the color of checkboxes in a ReactJS material table

I'm working on a project that involves using the Material table, and I need to change the color of the checkbox when it's selected. Can anyone help me with this? https://i.stack.imgur.com/JqVOU.png function BasicSelection() { return ( <M ...

What is the ideal project layout for a React and Node.js application?

With all the latest trends in the JS world, it can be a bit overwhelming to figure out the best way to organize files and folders for a project. While there are some examples from Facebook on GitHub, they tend to be quite basic. You can also check out som ...

Design a model class containing two arrow functions stored in variables with a default value

I am looking to create a model class with two variables (label and key) that store functions. Each function should take data as an input object. If no specific functions are specified, default functions should be used. The default label function will retur ...

Filter the output from a function that has the ability to produce a Promise returning a boolean value or a

I can't help but wonder if anyone has encountered this issue before. Prior to this, my EventHandler structure looked like: export interface EventHandler { name: string; canHandleEvent(event: EventEntity): boolean; handleEvent(event: EventEntity ...

Managing authentication within getStaticProps requests

I am working on integrating a NextJs application with its backend in Laravel. Currently, all of our API routes in Laravel are secured using Laravel Sanctum to enhance security and prevent cross-site scripting attacks. One challenge I am facing is that th ...

Exploring the directories: bundles, lib, lib-esm, and iife

As some libraries/frameworks prepare the application for publishing, they create a specific folder structure within the 'dist' directory including folders such as 'bundles', 'lib', 'lib-esm', and 'iife'. T ...

TypeORM issue - UnsupportedDataTypeError

Here is the entity file I'm working with, user.ts: @Entity('users') export class User { @PrimaryGeneratedColumn() id: number | undefined; @Column({ type: 'string', name: 'username', nullable: true }) username: s ...

Angular two - Communication between parent and children components using a shared service

I am currently working on establishing communication between child components, but according to the documentation, I need to utilize services for this purpose. However, I am facing challenges in accessing service information. When I try to assign the retur ...

Centering items in Material UI Grid

I'm currently grappling with understanding Material UI's grid system and implementing it in React.js, and I find it a bit perplexing. My goal is to center the spinner loader and text irrespective of viewport size. While I can manage to place the ...

Error in GitHub Action: Uploading NPM Package

I encountered an issue with the "publish npm package github action" name: Publish package on NPM on: release: types: [created] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@ ...

I'm encountering a TypeError where setGameState is not recognized as a function. Any suggestions on how to resolve this

Question about MainMenu.js file in reactjs import React,{ useContext, useState } from "react"; import { QuizContext } from "../Helpers/Contexts"; export default function MainMenu() { const {gameState, setGameState} = useState(QuizCont ...

Refresh React when updating an array with setState

I'm new to using React and Next.js and I have a question about the behavior of useState. When updating the state in this way [test, setTest] = useState("test"), the value on the webpage reloads, but not when using an object like in the code below. Can ...

Tips on expanding typings in TypeScript?

In my software library, there exists a map function with the following definitions: function map<T, U>(f: (x: T) => U, a: Array<T>): Array<U> function map<T, U>(f: (x: T) => U, a: Functor<T>): Functor<U> Furtherm ...

Generating additional react-select dropdowns depending on the selection made in the initial dropdown menu

I am currently working on a travel website. I am in need of a dropdown menu for users to select the number of children they will be traveling with, with options for 1, 2, or 3. If the user chooses 1 child, then an additional react-select element should ...

Do not use unnecessary variables for storing references in ES6

Despite using react es6, I am still unsure how to refrain from needing to use the variable that for this scenario: const that = this; UploadApi.exec(file).then(data => { that.setState({ loading : false}); }); ...

Troubleshooting an H20 error status when deploying a Next.js app on Heroku

I am relatively new to working with React and Next.js. I successfully developed a simple website in Next.js, and when I built the project on my local system using `npm run build`, it ran without any errors. However, when I tried to deploy it on Heroku, the ...

I desire to perform a specific task when there is a modification in the path using react router framework

Though I am mindful of it. props.history.listen((location, action) => { console.log("when route changes",location); }) However, I need to implement it in a slightly different way. For instance, let's cons ...

The React.js Redux reducer fails to add the item to the state

I'm just starting to learn about React.js and Redux. Currently, I am working on creating a basic shopping cart application. https://i.stack.imgur.com/BfvMY.png My goal is to have an item (such as a banana) added to the cart when clicked. (This sho ...