I have a FilterHeader component that gets some stores injected:
interface FilterHeaderProps {
applicationStore: ApplicationStoreType;
conversationsListStore: ConversationsListStoreType
}
@inject('applicationStore', 'conversationsListStore')
@observer
export default class FilterHeader extends Component<FilterHeaderProps> {
...
When I use the class in another component like this:
return(
<Container>
<FilterHeader />
<Panel>
...
TypeScript is complaining that it's missing the properties:
Type '{}' is missing the following properties from type 'Readonly<FilterHeaderProps>': applicationStore, conversationsListStorets(2739)
I've chased down the problem to the fact that TypeScript decorators are not able to change the function signature:
github.com/DefinitelyTyped/DefinitelyTyped/issues…
I've solved my problem so far by using functions:
const observedFilterHeader = observer(FilterHeader);
export default inject('applicationStore', 'conversationsListStore')<any>(observedFilterHeader);
By placing an <any> after the inject it works but TBH I was more guessing than knowing what's happening here. My questions are:
<any> and maybe how to write it in a better way?
Since you cannot mutate a class's type signature with a decorator, you have to use a function. Ultimately you have to tell Typescript what the function is going to do to the types, which is change them from required to optional. It provides a helper for this,
Partial. So you can:function Inject<T>(Component: React.Component<T>): React.Component<Partial<T>>That re-writes your original types into:
interface FilterHeaderProps { applicationStore?: ApplicationStoreType; conversationsListStore?: ConversationsListStoreType }There are two elaborations on that concept:
For the former, you can make the
Injectfunction above take two type parameters,function Inject<T,U>and return a component where only one of them is made optional (React.Component<Partial<T> & U>). But then you have to explicitly tell the function which is which, verbosely:interface FilterHeaderProps { applicationStore: ApplicationStoreType; conversationsListStore: ConversationsListStoreType; otherProp: string; } type Injected = Pick<FilterHeaderProps, 'applicationStore', 'conversationsListStore'>; type Required = Pick<FilterHeaderProps, 'otherProp'>; function Inject<T>(Component: React.Component<T>): React.Component<Partial<T>> { // ... } export default Inject<Injected, Required>(FilterHeaderComponent);For the latter, you'd have to make an interface for your stores somewhere, then annotate the Inject component to take it, something like:
interface Stores { applicationStore: ApplicationStoreType; conversationsListStore: ConversationsListStoreType; } function Inject<T extends Stores>(Component: React.Component<T>, stores: Array<keyof Stores>): React.Component<Partial<T>> { // stores is e.g. ['applicationStore', 'conversationsListStore'] return inject(...stores)(Component); }My syntax may not be precise but that is about what you have to do last I did this. I used this strategy in a large project and it was fine, but ultimately I did not like the verbosity and prefer just making the stores optional and using
!(e.g.this.props.applicationStore!.doTheThing()). In practice I never ran into any actual issues with the lack of type safety there. Yup if anyone knows of a less verbose way to achieve this in a typesafe way, i'd love to hear about it too.