Problem
I've wrapped a component with a typical HOC that I've used in some format on other non-TS projects. My problem is that I can't use the HOC's prop currentBreakpoint
inside of my wrapped component because it expects it to be in the component's type:
Property 'currentBreakpoint' does not exist on type 'Readonly<OwnProps> & Readonly<{ children?: ReactNode; }>'. TS2339
So then I import the HOC's props interface and merge it into my component's own prop interface but then I get:
Property 'currentBreakpoint' is missing in type '{}' but required in type 'Readonly<Props>'. TS2741
As it expects the prop currentBreakpoint
to be defined when I invoke <ChildComponent />
in my view – even though this is provided by the HOC.
-
Here are my files:
react-scripts@3.0.1
typescript@3.4.5
ChildComponent.js
import React, { Component } from 'react';
import withBreakpoints, {
Breakpoints,
Props as WithBreakpointsProps
} from 'lib/withBreakpoints';
interface OwnProps {}
type Props = WithBreakpointsProps & OwnProps;
class ChildComponent extends Component<Props> {
constructor(props: Props) {
super(props);
this.state = {
daysPerPage: this.getDaysPerPageFromViewportSize(),
cursor: new Date()
};
}
getDaysPerPageFromViewportSize = () => {
// the problem area
const { currentBreakpoint } = this.props;
let daysPerPage;
switch (currentBreakpoint) {
case Breakpoints.SMALL.label:
case Breakpoints.EXTRA_SMALL.label:
daysPerPage = 1;
break;
case Breakpoints.MEDIUM.label:
case Breakpoints.LARGE.label:
daysPerPage = 4;
break;
case Breakpoints.EXTRA_LARGE.label:
daysPerPage = 6;
break;
default:
daysPerPage = 1;
break;
}
return daysPerPage
};
render() {
return (
<div className="AvailabilityCalendar" />
);
}
}
export default withBreakpoints<Props>(AvailabilityCalendar);
withBreakpoints.ts
import React, { Component, ComponentType } from 'react';
export type CurrentBreakpoint = string | null;
export interface Props {
currentBreakpoint: string
}
export interface Breakpoint {
label: string;
lowerBound: number;
upperBound: number;
}
export interface State {
currentBreakpoint: CurrentBreakpoint;
}
export const Breakpoints: {
[id: string]: Breakpoint
} = {
EXTRA_SMALL: {
label: 'EXTRA_SMALL',
lowerBound: 0,
upperBound: 640
},
SMALL: {
label: 'SMALL',
lowerBound: 641,
upperBound: 1024
},
MEDIUM: {
label: 'MEDIUM',
lowerBound: 1025,
upperBound: 1280
},
LARGE: {
label: 'LARGE',
lowerBound: 1281,
upperBound: 1920
},
EXTRA_LARGE: {
label: 'EXTRA_LARGE',
lowerBound: 1921,
upperBound: 1000000
}
};
const withBreakpoints = <WrappedComponentProps extends object>(
WrappedComponent: ComponentType<WrappedComponentProps>
) => {
class WithBreakpoints extends Component<WrappedComponentProps, State> {
constructor(props: WrappedComponentProps) {
super(props);
this.state = {
currentBreakpoint: this.getCurrentBreakpoint()
};
}
componentDidMount() {
window.addEventListener('resize', this.checkBreakpoints);
}
componentWillUnmount() {
window.removeEventListener('resize', this.checkBreakpoints);
}
checkBreakpoints = () => {
let currentBreakpoint: CurrentBreakpoint = this.getCurrentBreakpoint();
if (currentBreakpoint !== this.state.currentBreakpoint) {
this.setState({ currentBreakpoint });
}
};
getCurrentBreakpoint = (): CurrentBreakpoint => {
const currentViewportWidth: number = Math.round(window.innerWidth);
return Object.keys(Breakpoints).find(
key =>
Breakpoints[key].lowerBound < currentViewportWidth &&
Breakpoints[key].upperBound > currentViewportWidth
) || null;
};
render() {
return (
<WrappedComponent
{...this.props as WrappedComponentProps}
currentBreakpoint={this.state.currentBreakpoint}
/>
);
}
}
return WithBreakpoints;
};
export default withBreakpoints;
-
Response to "Make currentBreakpoint
optional on the ChildComponent
".
I have seen this as the accepted answer on other questions but I think we can all agree this is an inappropriate use of the optional flag on properties and defeats the purpose of using Typescript.