UNPKG

3.81 kBPlain TextView Raw
1import React from "react"
2import { observer } from "./observer"
3import { copyStaticProperties } from "./utils/utils"
4import { MobXProviderContext } from "./Provider"
5import { IReactComponent } from "./types/IReactComponent"
6import { IValueMap } from "./types/IValueMap"
7import { IWrappedComponent } from "./types/IWrappedComponent"
8import { IStoresToProps } from "./types/IStoresToProps"
9
10/**
11 * Store Injection
12 */
13function createStoreInjector(
14 grabStoresFn: IStoresToProps,
15 component: IReactComponent<any>,
16 injectNames: string,
17 makeReactive: boolean
18): IReactComponent<any> {
19 // Support forward refs
20 let Injector: IReactComponent<any> = React.forwardRef((props, ref) => {
21 const newProps = { ...props }
22 const context = React.useContext(MobXProviderContext)
23 Object.assign(newProps, grabStoresFn(context || {}, newProps) || {})
24
25 if (ref) {
26 newProps.ref = ref
27 }
28
29 return React.createElement(component, newProps)
30 })
31
32 if (makeReactive) Injector = observer(Injector)
33 Injector["isMobxInjector"] = true // assigned late to suppress observer warning
34
35 // Static fields from component should be visible on the generated Injector
36 copyStaticProperties(component, Injector)
37 Injector["wrappedComponent"] = component
38 Injector.displayName = getInjectName(component, injectNames)
39 return Injector
40}
41
42function getInjectName(component: IReactComponent<any>, injectNames: string): string {
43 let displayName
44 const componentName =
45 component.displayName ||
46 component.name ||
47 (component.constructor && component.constructor.name) ||
48 "Component"
49 if (injectNames) displayName = "inject-with-" + injectNames + "(" + componentName + ")"
50 else displayName = "inject(" + componentName + ")"
51 return displayName
52}
53
54function grabStoresByName(
55 storeNames: Array<string>
56): (baseStores: IValueMap, nextProps: React.Props<any>) => React.PropsWithRef<any> | undefined {
57 return function (baseStores, nextProps) {
58 storeNames.forEach(function (storeName) {
59 if (
60 storeName in nextProps // prefer props over stores
61 )
62 return
63 if (!(storeName in baseStores))
64 throw new Error(
65 "MobX injector: Store '" +
66 storeName +
67 "' is not available! Make sure it is provided by some Provider"
68 )
69 nextProps[storeName] = baseStores[storeName]
70 })
71 return nextProps
72 }
73}
74
75export function inject(
76 ...stores: Array<string>
77): <T extends IReactComponent<any>>(
78 target: T
79) => T & (T extends IReactComponent<infer P> ? IWrappedComponent<P> : never)
80export function inject<S, P, I, C>(
81 fn: IStoresToProps<S, P, I, C>
82): <T extends IReactComponent>(target: T) => T & IWrappedComponent<P>
83
84/**
85 * higher order component that injects stores to a child.
86 * takes either a varargs list of strings, which are stores read from the context,
87 * or a function that manually maps the available stores from the context to props:
88 * storesToProps(mobxStores, props, context) => newProps
89 */
90export function inject(/* fn(stores, nextProps) or ...storeNames */ ...storeNames: Array<any>) {
91 if (typeof arguments[0] === "function") {
92 let grabStoresFn = arguments[0]
93 return (componentClass: React.ComponentClass<any, any>) =>
94 createStoreInjector(grabStoresFn, componentClass, grabStoresFn.name, true)
95 } else {
96 return (componentClass: React.ComponentClass<any, any>) =>
97 createStoreInjector(
98 grabStoresByName(storeNames),
99 componentClass,
100 storeNames.join("-"),
101 false
102 )
103 }
104}
105
\No newline at end of file