import { forwardRef, memo } from "react" import { isUsingStaticRendering } from "./staticRendering" import { useObserver } from "./useObserver" let warnObserverOptionsDeprecated = true const hasSymbol = typeof Symbol === "function" && Symbol.for const isFunctionNameConfigurable = Object.getOwnPropertyDescriptor(() => {}, "name")?.configurable ?? false // Using react-is had some issues (and operates on elements, not on types), see #608 / #609 const ReactForwardRefSymbol = hasSymbol ? Symbol.for("react.forward_ref") : typeof forwardRef === "function" && forwardRef((props: any) => null)["$$typeof"] const ReactMemoSymbol = hasSymbol ? Symbol.for("react.memo") : typeof memo === "function" && memo((props: any) => null)["$$typeof"] export interface IObserverOptions { readonly forwardRef?: boolean } export function observer

( baseComponent: React.ForwardRefRenderFunction, options: IObserverOptions & { forwardRef: true } ): React.MemoExoticComponent< React.ForwardRefExoticComponent & React.RefAttributes> > export function observer

( baseComponent: React.ForwardRefExoticComponent< React.PropsWithoutRef

& React.RefAttributes > ): React.MemoExoticComponent< React.ForwardRefExoticComponent & React.RefAttributes> > export function observer

( baseComponent: React.FunctionComponent

, options?: IObserverOptions ): React.FunctionComponent

export function observer< C extends React.FunctionComponent | React.ForwardRefRenderFunction, Options extends IObserverOptions >( baseComponent: C, options?: Options ): Options extends { forwardRef: true } ? C extends React.ForwardRefRenderFunction ? C & React.MemoExoticComponent< React.ForwardRefExoticComponent< React.PropsWithoutRef

& React.RefAttributes > > : never /* forwardRef set for a non forwarding component */ : C & { displayName: string } // n.b. base case is not used for actual typings or exported in the typing files export function observer

( baseComponent: | React.ForwardRefRenderFunction | React.FunctionComponent

| React.ForwardRefExoticComponent & React.RefAttributes>, // TODO remove in next major options?: IObserverOptions ) { if (process.env.NODE_ENV !== "production" && warnObserverOptionsDeprecated && options) { warnObserverOptionsDeprecated = false console.warn( `[mobx-react-lite] \`observer(fn, { forwardRef: true })\` is deprecated, use \`observer(React.forwardRef(fn))\`` ) } if (ReactMemoSymbol && baseComponent["$$typeof"] === ReactMemoSymbol) { throw new Error( `[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.` ) } // The working of observer is explained step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 if (isUsingStaticRendering()) { return baseComponent } let useForwardRef = options?.forwardRef ?? false let render = baseComponent const baseComponentName = baseComponent.displayName || baseComponent.name // If already wrapped with forwardRef, unwrap, // so we can patch render and apply memo if (ReactForwardRefSymbol && baseComponent["$$typeof"] === ReactForwardRefSymbol) { useForwardRef = true render = baseComponent["render"] if (typeof render !== "function") { throw new Error( `[mobx-react-lite] \`render\` property of ForwardRef was not a function` ) } } let observerComponent = (props: any, ref: React.Ref) => { return useObserver(() => render(props, ref), baseComponentName) } // Inherit original name and displayName, see #3438 ;(observerComponent as React.FunctionComponent).displayName = baseComponent.displayName if (isFunctionNameConfigurable) { Object.defineProperty(observerComponent, "name", { value: baseComponent.name, writable: true, configurable: true }) } // Support legacy context: `contextTypes` must be applied before `memo` if ((baseComponent as any).contextTypes) { ;(observerComponent as React.FunctionComponent).contextTypes = ( baseComponent as any ).contextTypes } if (useForwardRef) { // `forwardRef` must be applied prior `memo` // `forwardRef(observer(cmp))` throws: // "forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))" observerComponent = forwardRef(observerComponent) } // memo; we are not interested in deep updates // in props; we assume that if deep objects are changed, // this is in observables, which would have been tracked anyway observerComponent = memo(observerComponent) copyStaticProperties(baseComponent, observerComponent) if ("production" !== process.env.NODE_ENV) { Object.defineProperty(observerComponent, "contextTypes", { set() { throw new Error( `[mobx-react-lite] \`${ this.displayName || this.type?.displayName || this.type?.name || "Component" }.contextTypes\` must be set before applying \`observer\`.` ) } }) } return observerComponent } // based on https://github.com/mridgway/hoist-non-react-statics/blob/master/src/index.js const hoistBlackList: any = { $$typeof: true, render: true, compare: true, type: true, // Don't redefine `displayName`, // it's defined as getter-setter pair on `memo` (see #3192). displayName: true } function copyStaticProperties(base: any, target: any) { Object.keys(base).forEach(key => { if (!hoistBlackList[key]) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(base, key)!) } }) }