Last active
April 5, 2019 05:34
-
-
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`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" /> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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