Skip to content

Instantly share code, notes, and snippets.

@ipsips
Last active April 5, 2019 05:34
Show Gist options
  • Select an option

  • Save ipsips/1dcb1ee7e2ccfcb2a54b0a4c52596000 to your computer and use it in GitHub Desktop.

Select an option

Save ipsips/1dcb1ee7e2ccfcb2a54b0a4c52596000 to your computer and use it in GitHub Desktop.
1) Use `withStores` in stead of `mobx.inject` to work around the problem that injected stores must be defined as optional props individually. 2) Also export plain undecorated component for the use case of `enzyme.shallow`
import SomeReactComponent from './SomeReactComponent';
// The React component in use elsewhere: TS complains if you omit `someProp` but does not force you to provide all the required stores here as props.
<SomeReactComponent someProp="hello" />
import React from 'react';
import { inject, observer, IWrappedComponent } from 'mobx-react';
import withStores from './withStores';
import { RootStore } from '@store/RootStore';
// No need to import classes of all the required stores for typing.
enum RequiredStores {
'translateStore',
'localeStore',
'navigationStore',
'onlineHelpStore',
'modalStore',
'deliveryAddressStore',
'messageStore'
}
// Here we list component-specific props and extend the interface with a subset of RootStore by picking above-defined enum keys.
// This effectively adds required stores as required props and this is where withStores comes into play allowing
// the stores to be required statically but actually be injected runtime.
export interface SomeReactComponentProps extends Pick<RootStore, keyof typeof RequiredStores> {
someProp: string;
// No need to define stores individually as optional props. before we had to do:
// translateStore?: TranslateStore;
// localeStore?: LocaleStore;
// navigationStore?: NavigationStore;
// ...
}
// We also export undecorated component that can be used in unit tests where required stores can be passed
// directly as props (in lieu of useing <Provider> and @inject) and enzyme.shallow rendering can be properly used.
export class SomeReactComponent extends React.Component<SomeReactComponentProps> {
render(): JSX.Element {
const messageStore = this.props.messageStore; // auto-complete works here FTW.
return null;
}
}
// No need to list all the required store names again as you normally would with inject: @inject('translateStore', 'localeStore', 'navigationStore', ...)
export default withStores(Object.values(RequiredStores))(observer(SomeReactComponent));
type Omit<ALL, DEL> = Pick<ALL, Exclude<keyof ALL, DEL>>;
type Subtract<T, K> = Omit<T, keyof K>;
// credit goes to author: https://github.com/mobxjs/mobx-react/issues/256#issuecomment-479396805
const withStores = <TStoreProps extends keyof RootStore>(stores: TStoreProps[]) =>
<TProps extends Pick<RootStore, TStoreProps>>(component: React.ComponentType<TProps>) => (
(inject(...stores)(component) as any) as
React.FC<Subtract<TProps, Pick<RootStore, TStoreProps>> &
Partial<Pick<RootStore, TStoreProps>>> &
IWrappedComponent<TProps>
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment