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??