Mastering Typing for Enhanced Order Components using Recompose and TypeScript

I have been working on integrating recompose into my react codebase. As part of this process, I have been experimenting with getting some basic functionality to work. While I have made progress, I am uncertain if I am following the correct approach for using recompose.

Let me show you a snippet of my code:

interface Value {
  value: string
}

interface Loading {
  loading: boolean
}

const TestComponent = (props: Value) => <div>Value: {props.value}</div>
const LoadingComponent = () => <div>Loading ...</div>

In the code above, TestComponent is meant to display the provided value in its props. Additionally, there is a LoadingComponent that should be displayed when the loading prop is set.

To implement this functionality, I utilized the branch function from recompose

const withLoading = branch<Loading>(
  ({loading}) => loading, 
  renderComponent(LoadingComponent)
) 

When I apply withLoading to any Component without props, I can set the loading prop on them.

const EnhancedCompWithLoading = withLoading(SomeCompWithoutProps)

render() {
  return <EnhancedCompWithLoading loading={this.state.loading} />
}

This works well, but issues arise when trying to use this with Components that already have props. For example:

const TestWithLoading = withLoading(TestComponent)

render() {
  return <TestWithLoading value="testVal" loading={this.state.loading} />
}

Upon attempting the above, I encounter the error message

TS2339: Property 'value' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<Loading, any, any>>
& Readonly<{ children?: ReactNode; }> & Readonly<Loading>'.

This led me to inspect the type definition in @types/recompose. It appears that branch<TOutter> returns a

ComponentEnhancer<any,TOutter>
. While this allows for providing any component and informs the resulting component about the required props for the test function, it seems to fall short when additional props are involved.

The TypeDefinition for the ComponentEnhancer looks like this (recompose 0.30.2):

interface ComponentEnhancer<TInner, TOutter> {
  (component: Component<TInner>): ComponentClass<TOutter>
}

Essentially, the

ComponentEnhancer<any, Loading>
received from the previous branch function will return a ComponentClass<Loading>. However, the <TInner> of the component provided to the ComponentEnhancer gets discarded, making it challenging to utilize the Value props in the Enhanced Component.

My question is whether I am approaching this incorrectly, if there is a better way to accomplish this with recompose, or if it could potentially be a bug in the TypeDeclaration. Changing the return of the ComponentEnhancer to

ComponentClass<TOutter & TInner>
resolves the issue for me. Any insights on this matter?

Answer №1

I haven't come across the concepts of branch and recompose before, but if it's just a minor error in typing, we can certainly rectify it.

The issue seems to stem from the inadequate type definition for branch. If the intention is to pass on the properties of the wrapped component to the HOC, while explicitly adding props typed to branch, then a more appropriate type for the outcome would be:

type EnhancedComponent<TOuter> = {
    <TInner>(component: React.ComponentType<TInner>): React.ComponentClass<TInner & TOuter>
}

By utilizing this type, the following code snippet will function correctly:

const withLoading = branch<Loading>(
    ({ loading }) => loading,
    renderComponent(LoadingComponent)
) as unknown as EnhancedComponent<Loading>

type EnhancedComponent<TOuter> = {
    <TInner>(component: React.ComponentType<TInner>): React.ComponentClass<TInner & TOuter>
}

const TestWithLoading = withLoading(TestComponent)

function display() {
    return <TestWithLoading value="testVal" loading={true} />
}

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

Unable to perform Undo function in monaco editor

Currently in my Angular 7 project, I have integrated the Monaco editor for coding purposes. One issue I am facing is that when I make a change to the code and then press ctrl+z to undo it, the previous code is successfully restored. However, if I change th ...

Top recommendation for utilizing $scope variables in Angular applications

Currently, I am working on a new project and I want to ensure that I am correctly utilizing $scope. After watching an informative video on best practices, Miško mentioned that manipulating $scope properties directly may not be the best approach. Typical ...

Overabundance of Recursive Calls in Node.js Project Dependencies

After a tiring day at work, I noticed an alert for Windows SkyDrive showing that files couldn't be uploaded due to the path being too long. The lengthy directory structure made me chuckle at the technological limitation. However, it got me thinking: ...

Leveraging Emotion API in Video Content (JavaScript or Ruby)

Currently, I'm in the process of uploading a video to the Emotion API for videos, but unfortunately, I have not received any response yet. I managed to successfully upload it using the Microsoft online console. However, my attempts to integrate it in ...

Accessing the Angular scope and making modifications using Chrome browser

I need to access the $scope value in order to update its data values via a chrome extension. I have attempted to obtain the $scope using the code below: var $scope = angular.element(document.getElementById('name')).scope() While this code wor ...

Leveraging jQuery Ajax and MySQL to generate dynamic HTML content

I'm in the process of creating a unique photo gallery that utilizes dynamic features. Instead of relying on constant HTML/PHP refreshing for updates, I am incorporating jQuery to handle dynamic MYSQL queries. As a beginner, I've managed to creat ...

Am I on the right track in my understanding of how document and viewport relate to mouse position in JavaScript?

After reviewing responses from a previous inquiry, it is evident that both pertain to the x and y coordinates of mouse positions. In relation to the document and In relation to the viewport. I have delved into an article on QuirksMode, yet I feel ther ...

Unleashing the full power of Node.JS asynchronous operations

I've been struggling to grasp how to effectively manage the asynchronous nature of Node.JS. Despite reading extensively on the topic and experimenting with message passing and callback functions, I can't seem to get my object constructor to load ...

Tips for customizing the color of Menu in material-ui v5

I've been searching for solutions to change the background color of the Menu, but the methods I found are outdated. The use of @mui/styles and makeStyles is now deprecated, as stated in mui.com/styles/basics/#hook-api. I attempted to change the backgr ...

How can I insert a item into an Array using JavaScript code?

My instructor set up an array in my JavaScript file that is off limits for me to modify. My task is to add new objects to it through code without directly manipulating the existing array. Here's a snapshot of what my array contains: const words = [{ ...

Accessing form data from Ajax/Jquery in php using $_POST variables

Thank you in advance for any assistance on this matter. I'm currently attempting to utilize Ajax to call a script and simultaneously post form data. While everything seems to be working correctly, the $POST data appears to come back blank when trying ...

Having trouble locating the correct JSON array syntax for Highcharts

Hey there! I'm currently facing a bit of a challenge while trying to set up a JSON array using PHP and integrate it into Highcharts. Currently, I am creating the array in this manner: $stack[] = array($commname => $countit); $stack = json_encode( ...

Why is the console log not working on a library that has been imported into a different React component?

Within my 'some-library' project, I added a console.log("message from some library") statement in the 'some-component.js' file. However, when I import 'some-component' from 'some-library' after running uglifyjs with ...

Step-by-step guide on redirecting to a different page in NextJS if a dynamic route cannot be found

I need assistance with redirecting users when they access a dynamic route that does not exist. The dynamic route points to a field within a statically defined array of fields. Here is the code snippet for the page: import { fields } from "@/data/fiel ...

Customize your Shopify Messenger icon using JavaScript!

Shopify recently introduced a new feature that allows customers to contact store owners through messenger. My client has requested me to customize the appearance of this icon with their branded icon instead. https://i.stack.imgur.com/uytpd.png I have not ...

Transform text that represents a numerical value in any base into an actual number

Looking to convert a base36 string back to a double value. The original double is 0.3128540377812142. When converting it to base 36: (0.3128540377812142).toString(36); The results are : Chrome: 0.b9ginb6s73gd1bfel7npv0wwmi Firefox: 0.b9ginb6s73e Now, h ...

angular trustAsHtml does not automatically insert content

Two divs are present on the page. Upon clicking button1, an iframe is loaded into div1. The same applies to button2 and div2. These iframes are loaded via ajax and trusted using $sce.trustAsHtml. This is how the HTML looks: <div ng-bind-html="video.tru ...

Using node.js to download files with axios, streaming the content, and then

I am attempting to download a PDF file using axios, and save it on the server side using fs.writeFile. My code is as follows: axios.get('https://xxx/my.pdf', {responseType: 'blob'}).then(response => { fs.writeFile('/temp/my ...

Have the functionality of right clicking and selecting Paste work in the input field using style=text and a button

Here is the code I am using: <script type="text/javascript"> $(document).ready(function() { $('#submit').prop('disabled', true); $('#links').change(function() { $('#submit').prop('disabled ...

Angular 5 offers the ability to incorporate dynamic checkbox input into your application

Here is my code snippet: <input [type]="'checkbox'" [(ngModel)]="inputValue"> <p>Value: {{ inputValue }}</p> I'm puzzled as to why the value in inputValue remains unchanged. Can anyone shed light on this? I am unable to ...