UNPKG

6.47 kBPlain TextView Raw
1import { forwardRef, memo } from "react"
2
3import { isUsingStaticRendering } from "./staticRendering"
4import { useObserver } from "./useObserver"
5
6let warnObserverOptionsDeprecated = true
7
8const hasSymbol = typeof Symbol === "function" && Symbol.for
9const isFunctionNameConfigurable =
10 Object.getOwnPropertyDescriptor(() => {}, "name")?.configurable ?? false
11
12// Using react-is had some issues (and operates on elements, not on types), see #608 / #609
13const ReactForwardRefSymbol = hasSymbol
14 ? Symbol.for("react.forward_ref")
15 : typeof forwardRef === "function" && forwardRef((props: any) => null)["$$typeof"]
16
17const ReactMemoSymbol = hasSymbol
18 ? Symbol.for("react.memo")
19 : typeof memo === "function" && memo((props: any) => null)["$$typeof"]
20
21export interface IObserverOptions {
22 readonly forwardRef?: boolean
23}
24
25export function observer<P extends object, TRef = {}>(
26 baseComponent: React.ForwardRefRenderFunction<TRef, P>,
27 options: IObserverOptions & { forwardRef: true }
28): React.MemoExoticComponent<
29 React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>
30>
31
32export function observer<P extends object, TRef = {}>(
33 baseComponent: React.ForwardRefExoticComponent<
34 React.PropsWithoutRef<P> & React.RefAttributes<TRef>
35 >
36): React.MemoExoticComponent<
37 React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>
38>
39
40export function observer<P extends object>(
41 baseComponent: React.FunctionComponent<P>,
42 options?: IObserverOptions
43): React.FunctionComponent<P>
44
45export function observer<
46 C extends React.FunctionComponent<any> | React.ForwardRefRenderFunction<any>,
47 Options extends IObserverOptions
48>(
49 baseComponent: C,
50 options?: Options
51): Options extends { forwardRef: true }
52 ? C extends React.ForwardRefRenderFunction<infer TRef, infer P>
53 ? C &
54 React.MemoExoticComponent<
55 React.ForwardRefExoticComponent<
56 React.PropsWithoutRef<P> & React.RefAttributes<TRef>
57 >
58 >
59 : never /* forwardRef set for a non forwarding component */
60 : C & { displayName: string }
61
62// n.b. base case is not used for actual typings or exported in the typing files
63export function observer<P extends object, TRef = {}>(
64 baseComponent:
65 | React.ForwardRefRenderFunction<TRef, P>
66 | React.FunctionComponent<P>
67 | React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>,
68 // TODO remove in next major
69 options?: IObserverOptions
70) {
71 if (process.env.NODE_ENV !== "production" && warnObserverOptionsDeprecated && options) {
72 warnObserverOptionsDeprecated = false
73 console.warn(
74 `[mobx-react-lite] \`observer(fn, { forwardRef: true })\` is deprecated, use \`observer(React.forwardRef(fn))\``
75 )
76 }
77
78 if (ReactMemoSymbol && baseComponent["$$typeof"] === ReactMemoSymbol) {
79 throw new Error(
80 `[mobx-react-lite] You are trying to use \`observer\` on a function component wrapped in either another \`observer\` or \`React.memo\`. The observer already applies 'React.memo' for you.`
81 )
82 }
83
84 // The working of observer is explained step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307
85 if (isUsingStaticRendering()) {
86 return baseComponent
87 }
88
89 let useForwardRef = options?.forwardRef ?? false
90 let render = baseComponent
91
92 const baseComponentName = baseComponent.displayName || baseComponent.name
93
94 // If already wrapped with forwardRef, unwrap,
95 // so we can patch render and apply memo
96 if (ReactForwardRefSymbol && baseComponent["$$typeof"] === ReactForwardRefSymbol) {
97 useForwardRef = true
98 render = baseComponent["render"]
99 if (typeof render !== "function") {
100 throw new Error(
101 `[mobx-react-lite] \`render\` property of ForwardRef was not a function`
102 )
103 }
104 }
105
106 let observerComponent = (props: any, ref: React.Ref<TRef>) => {
107 return useObserver(() => render(props, ref), baseComponentName)
108 }
109
110 // Inherit original name and displayName, see #3438
111 ;(observerComponent as React.FunctionComponent).displayName = baseComponent.displayName
112
113 if (isFunctionNameConfigurable) {
114 Object.defineProperty(observerComponent, "name", {
115 value: baseComponent.name,
116 writable: true,
117 configurable: true
118 })
119 }
120
121 // Support legacy context: `contextTypes` must be applied before `memo`
122 if ((baseComponent as any).contextTypes) {
123 ;(observerComponent as React.FunctionComponent).contextTypes = (
124 baseComponent as any
125 ).contextTypes
126 }
127
128 if (useForwardRef) {
129 // `forwardRef` must be applied prior `memo`
130 // `forwardRef(observer(cmp))` throws:
131 // "forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))"
132 observerComponent = forwardRef(observerComponent)
133 }
134
135 // memo; we are not interested in deep updates
136 // in props; we assume that if deep objects are changed,
137 // this is in observables, which would have been tracked anyway
138 observerComponent = memo(observerComponent)
139
140 copyStaticProperties(baseComponent, observerComponent)
141
142 if ("production" !== process.env.NODE_ENV) {
143 Object.defineProperty(observerComponent, "contextTypes", {
144 set() {
145 throw new Error(
146 `[mobx-react-lite] \`${
147 this.displayName || this.type?.displayName || this.type?.name || "Component"
148 }.contextTypes\` must be set before applying \`observer\`.`
149 )
150 }
151 })
152 }
153
154 return observerComponent
155}
156
157// based on https://github.com/mridgway/hoist-non-react-statics/blob/master/src/index.js
158const hoistBlackList: any = {
159 $$typeof: true,
160 render: true,
161 compare: true,
162 type: true,
163 // Don't redefine `displayName`,
164 // it's defined as getter-setter pair on `memo` (see #3192).
165 displayName: true
166}
167
168function copyStaticProperties(base: any, target: any) {
169 Object.keys(base).forEach(key => {
170 if (!hoistBlackList[key]) {
171 Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(base, key)!)
172 }
173 })
174}