Reputation: 1070
I'm working on a TypeScript/React project and we're building a generic 'Wizard' component for multi-step processes. The Wizard is a wrapper that takes an array of 'panel' component properties, and exposes navigation logic to each of them through an IWizardPanelProps interface.
The implementation of the Wizard component and the panel interfaces is below:
export interface IWizardPanelProps {
changePanel: (increment: number) => void;
}
export class WizardPanel<T> extends React.Component<IWizardPanelProps & T>{
constructor(props: IWizardPanelProps & T) {
super(props);
}
}
interface IWizardProps {
panelComponents: (typeof WizardPanel)[],
showControls?: boolean // Hidden by default - assumes panels themselves handle pagination
}
interface IWizardState {
panelIndex: number
}
export class Wizard extends React.Component<IWizardProps, IWizardState> {
constructor(props: IWizardProps) {
super(props);
this.state = {
panelIndex: 0
}
}
changePanel = (increment: number) => {
const newIndex = this.state.panelIndex + increment;
this.setState({ panelIndex: newIndex });
};
render() {
const { panelComponents, showControls } = this.props;
return (
<div>
<p><strong>Ye olde Wizard Component! (This is a placeholder, but the functionality is mostly there)</strong></p>
{panelComponents.map((Panel, key) => (
<div className={key === this.state.panelIndex ? undefined : 'hidden'} key={key}>{<Panel {...this.props} changePanel={this.changePanel}></Panel>}</div>
))}
{showControls && <div>
<button disabled={this.state.panelIndex === 0} onClick={() => this.changePanel(-1)}>
Previous
</button>
<button disabled={this.state.panelIndex === panelComponents.length - 1} onClick={() => this.changePanel(1)}>
Next
</button>
</div>}
</div>
);
}
}
Then when we create a panel component, we do so like this:
interface IMyPanelProps {
...
}
export class MyPanel extends WizardPanel<IMyPanelProps> {
constructor(props: IWizardPanelProps & IMyPanelProps) {
super(props);
}
render() {
...
}
...
}
Good so far right?
But then when we go to implement a Wizard like so:
<Wizard panelComponents={[ MyPanel ]}></Wizard>
We get the following error:
Type '(typeof MyPanel)[]' is not assignable to type '(typeof WizardPanel)[]'.
Type 'typeof MyPanel' is not assignable to type 'typeof WizardPanel'.
Type 'MyPanel' is not assignable to type 'WizardPanel'.
Types of property 'props' are incompatible.
Type 'Readonly<{ children?: ReactNode; }> & Readonly< IWizardPanelProps & IMyPanelProps>' is not assignable to type 'Readonly< { children?: ReactNode; }> & Readonly< IWizardPanelProps & T>'.
Type 'Readonly<{ children?: ReactNode; }> & Readonly < IWizardPanelProps & IMyPanelProps>' is not assignable to type 'Readonly < IWizardPanelProps & T>'.
What gives? It seems like it comes down to this last line:
Readonly<{ children?: ReactNode; }> & Readonly< IWizardPanelProps & IMyPanelProps>' is not assignable to type 'Readonly< IWizardPanelProps & T>
but I don't understand what I've done wrong. How are we missing the Readonly<{ children?: ReactNode; }>
in the IWizardProps declaration of propComponents??
Upvotes: 2
Views: 1349
Reputation: 249506
You can use the React.ComponentType
type. That is the intended way to define a component type.
interface IWizardProps {
panelComponents: React.ComponentType<IWizardPanelProps>)[],
showControls?: boolean
}
The problem is that WizardPanel
is generic and the assigned class is not and this causes an incompatibility between types. This would work as well for example :
export class BasicWizardPanel extends WizardPanel<{}>{}
interface IWizardProps {
panelComponents: (typeof BasicWizardPanel)[],
showControls?: boolean // Hidden by default - assumes panels themselves handle pagination
}
Upvotes: 2