UNPKG

3.92 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): (
57 baseStores: IValueMap,
58 nextProps: React.ClassAttributes<any>
59) => React.PropsWithRef<any> | undefined {
60 return function (baseStores, nextProps) {
61 storeNames.forEach(function (storeName) {
62 if (
63 storeName in nextProps // prefer props over stores
64 )
65 return
66 if (!(storeName in baseStores))
67 throw new Error(
68 "MobX injector: Store '" +
69 storeName +
70 "' is not available! Make sure it is provided by some Provider"
71 )
72 nextProps[storeName] = baseStores[storeName]
73 })
74 return nextProps
75 }
76}
77
78export function inject(
79 ...stores: Array<string>
80): <T extends IReactComponent<any>>(
81 target: T
82) => T & (T extends IReactComponent<infer P> ? IWrappedComponent<P> : never)
83export function inject<S extends IValueMap = {}, P extends IValueMap = {}, I extends IValueMap = {}, C extends IValueMap = {}>(
84 fn: IStoresToProps<S, P, I, C>
85): <T extends IReactComponent>(target: T) => T & IWrappedComponent<P>
86
87/**
88 * higher order component that injects stores to a child.
89 * takes either a varargs list of strings, which are stores read from the context,
90 * or a function that manually maps the available stores from the context to props:
91 * storesToProps(mobxStores, props, context) => newProps
92 */
93export function inject(/* fn(stores, nextProps) or ...storeNames */ ...storeNames: Array<any>) {
94 if (typeof arguments[0] === "function") {
95 let grabStoresFn = arguments[0]
96 return (componentClass: React.ComponentClass<any, any>) =>
97 createStoreInjector(grabStoresFn, componentClass, grabStoresFn.name, true)
98 } else {
99 return (componentClass: React.ComponentClass<any, any>) =>
100 createStoreInjector(
101 grabStoresByName(storeNames),
102 componentClass,
103 storeNames.join("-"),
104 false
105 )
106 }
107}