facebook/relay · error

Observer must be an Object with callbacks: ${String(observer

Error message

Observer must be an Object with callbacks: ${String(observer)}

What it means

`subscribe()` must receive an observer — an object with callbacks such as `next`, `error`, `complete` (or a sink). This dev-mode check throws when subscribing with a non-object (e.g. a bare function or undefined). Unlike some libraries, RelayObservable does not accept a bare callback function as shorthand for `{next: fn}`.

Source

Thrown at packages/relay-runtime/network/RelayObservable.js:323

        current = subscription;
      }
      return () => {
        current && current.unsubscribe();
      };
    });
  }

  /**
   * Observable's primary API: returns an unsubscribable Subscription to the
   * source of this Observable.
   *
   * Note: A sink may be passed directly to .subscribe() as its observer,
   * allowing for easily composing Observables.
   */
  subscribe(observer: Observer<T> | Sink<T>): Subscription {
    if (__DEV__) {
      // Early runtime errors for ill-formed observers.
      if (!observer || typeof observer !== 'object') {
        throw new Error(
          'Observer must be an Object with callbacks: ' + String(observer),
        );
      }
    }
    return subscribe(this._source, observer);
  }

  /**
   * Returns a new Observerable where each value has been transformed by
   * the mapping function.
   */
  map<U>(fn: T => U): RelayObservable<U> {
    return RelayObservable.create(sink => {
      const subscription = this.subscribe({
        complete: sink.complete,
        error: sink.error,
        next: value => {

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Pass an object: `observable.subscribe({next: value => ...})`
  2. If you have a bare callback, wrap it as `{next: callback}`
  3. Include `error` and `complete` handlers in the observer object to avoid swallowed errors

Example fix

// before
observable.subscribe(value => console.log(value));
// after
observable.subscribe({
  next: value => console.log(value),
  error: err => console.error(err),
  complete: () => console.log('done'),
});
Defensive patterns

Strategy: type-guard

Validate before calling

function safeSubscribe(obs, observer) {
  if (observer == null || typeof observer !== 'object') {
    throw new TypeError('subscribe requires an observer object like {next, error, complete}');
  }
  return obs.subscribe(observer);
}

Type guard

function isObserver(o: unknown): o is {next?: (v: unknown) => void; error?: (e: unknown) => void; complete?: () => void} {
  return typeof o === 'object' && o !== null;
}

Try / catch

try {
  observable.subscribe(observer);
} catch (e) {
  if (e.message.startsWith('Observer must be an Object')) {
    if (typeof observer === 'function') observable.subscribe({next: observer});
    else throw new TypeError('Provide an observer object: {next, error, complete}', {cause: e});
  } else throw e;
}

Prevention

When it happens

Trigger: `observable.subscribe(fn)` passing a function instead of `{next: fn}`; `observable.subscribe()` with no argument; subscribing with a primitive.

Common situations: Migrating from RxJS-style `subscribe(callback)` shorthand; forgetting the observer entirely; destructuring mistakes that lose the object.

Related errors


AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02). Data as JSON: /api/errors/121af9ea80016bdf. Report an issue: GitHub.