Reputation: 419
I am trying to write a higher order function for React in typescript that: (1) Requires a certain properties on the component being wrapped (2) Allows for the wrapped components properties to be set on the wrapper (3) Has properties specific to the wrapper
I mostly have things working, but when I go to set default properties on the anonymous class that wraps my component I get an error from typescript that I have not been able to resolve.
The error I get is:
src/withContainer.tsx:33:3 - error TS2322: Type 'typeof (Anonymous class)' is not assignable to type 'ComponentClass<P & ContainerProps, any>'.
Types of property 'defaultProps' are incompatible.
Type '{ loading: Element; }' is not assignable to type 'Partial<P & ContainerProps>'.
33 return class extends React.Component<P & ContainerProps, ContainerState> {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
34 // Why does typescript say this is an error?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
80 }
~~~~~
81 };
~~~~
Here is an example of what I am trying to do:
import * as React from "react";
/**
* Properties specific to the container component.
*/
export interface ContainerProps {
/* eslint-disable @typescript-eslint/no-explicit-any */
loading: React.ReactElement<any>;
}
export interface ContainerState {
data: {};
initialized: boolean;
}
/**
* Components being wrapped need a putData function on them.
*/
export interface PuttableProps {
/**
* Put data into state on the parent component.
*
* @param data Data to be put into state.
*/
putData(data: object): void;
}
/* eslint-disable max-lines-per-function */
export function withContainer<P>(
WrappedComponent: React.ComponentType<P & PuttableProps>
): React.ComponentClass<P & ContainerProps> {
return class extends React.Component<P & ContainerProps, ContainerState> {
// Why does typescript say this is an error?
static defaultProps = {
loading: <React.Fragment />
};
state: ContainerState = {
initialized: false,
data: {}
};
/**
* After mounting, simulate loading data and mark initialized.
*/
componentDidMount(): void {
// Simulate remote data load, 2 minutes after we mounted set initialized to true
setTimeout(() => {
this.setState({
initialized: true
});
}, 2000);
}
/**
* Set data as state on the parent component.
*/
private putData = (data: object): void => {
this.setState({ data });
};
/**
* Render the wrapped component.
*/
render(): React.ReactNode {
// If we haven't initialized the document yet, don't return the component
if (!this.state.initialized) {
return this.props.loading;
}
// Whatever props were passed from the parent, our data and our putData function
const props = {
...this.props,
...this.state.data,
putData: this.putData
};
return <WrappedComponent {...props} />;
}
};
}
export class ClickCounter extends React.Component<
PuttableProps & { count: number },
{}
> {
static defaultProps = {
count: 0
};
increment = () => {
this.props.putData({ count: this.props.count + 1 });
};
render(): React.ReactNode {
return (
<div>
<h1>{this.props.count}</h1>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
And it can be leveraged like this:
import React from "react";
import ReactDOM from "react-dom";
import { withContainer, ClickCounter } from "./withContainer";
const WrappedClickCounter = withContainer(ClickCounter);
const component = (
<WrappedClickCounter count={0} loading={<div>Loading...</div>} />
);
ReactDOM.render(component, document.getElementById("root"));
I've tried a few variations of this, including having P extend ContainerProps but nothing seems to work.
I am using typescript 3.3.1.
Upvotes: 3
Views: 1026
Reputation: 78850
As the error says, your defaultProps
don't match your ContainerProps
type. Specifically, in ContainerProps
, you have { isLoading: ReactElement<any> }
, but your defaultProps
has type { loading: Element }
.
I'd recommend you define ContainerProps
as this instead:
import type { ReactNode } from 'react';
export interface ContainerProps {
isLoading: ReactNode
}
Upvotes: 1
Reputation: 1367
Did you ever solve this? I on the line where you return the component, have you tried using Partial?:
return class extends React.Component<P & ContainerProps...
Can you try this instead?
return class extends React.Component<Partial<P & ContainerProps>....
Upvotes: 0