mobxjs/mobx · error

[mobx-react-lite] You are trying to use `observer` on a func

Error message

[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.

What it means

mobx-react-lite's `observer` already wraps the component in React.memo, so double-wrapping breaks the optimization and is rejected. This throw fires when the passed component's `$$typeof` equals `ReactMemoSymbol`, i.e. the input is a memoized component — which is exactly what `observer(...)` returns or what `React.memo(...)` produces. It is a developer-time contract check to prevent nested memo wrappers.

Source

Thrown at packages/mobx-react-lite/src/observer.ts:43

): React.FunctionComponent<P> & React.MemoExoticComponent<React.FunctionComponent<P>>

export function observer<P extends object, TRef = {}>(
    baseComponent: React.ForwardRefExoticComponent<
        React.PropsWithoutRef<P> & React.RefAttributes<TRef>
    >
): React.MemoExoticComponent<
    React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>
>

// n.b. base case is not used for actual typings or exported in the typing files
export function observer<P extends object, TRef = {}>(
    baseComponent:
        | React.ForwardRefRenderFunction<TRef, P>
        | React.FunctionComponent<P>
        | React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>
) {
    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 = 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

View on GitHub (pinned to 01211a698b)

Solutions

  1. Pass the plain function component to `observer` and remove the outer `React.memo` — observer applies memoization itself.
  2. If wrapping multiple concerns with other HOCs, apply `observer` to the raw component and let other HOCs wrap the observer result.
  3. If you receive an already-observed component, use it directly instead of re-wrapping with `observer`.

Example fix

// before
const Cmp = React.memo(function MyComp(props) { return <div>{props.x}</div> })
export default observer(Cmp)

// after
function MyComp(props) { return <div>{props.x}</div> }
export default observer(MyComp)
Defensive patterns

Strategy: validation

Validate before calling

function isMemoWrapped(Cmp) {
  return Cmp && Cmp.$$typeof === Symbol.for('react.memo')
}
// before wrapping:
if (isMemoWrapped(Base)) Base = Base.type // unwrap memo before observer(Base)
observer(Base)

Type guard

const isMemoComponent = (c) => !!c && typeof c === 'object' && c.$$typeof === Symbol.for('react.memo')

Prevention

When it happens

Trigger: Calling `observer(React.memo(Component))` or `observer(observer(Component))` — any function component whose `$$typeof` is `Symbol(react.memo)`.

Common situations: Incrementally migrating from manual React.memo to observer; composing HOCs in a pipeline where a component was already wrapped with observer; copy-pasting a memoized component into an observer call.

Related errors


AI-assisted analysis of mobxjs/mobx@01211a698b (2026-08-28). Data as JSON: /api/errors/b05a9fb6e3ab5736. Report an issue: GitHub.