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
- Pass an object: `observable.subscribe({next: value => ...})`
- If you have a bare callback, wrap it as `{next: callback}`
- 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
- Always pass an object literal {next, error, complete} to subscribe — no bare-function shorthand like RxJS
- Never call subscribe() with no arguments
- Include an error handler so errors aren't swallowed
- Add a lint rule or code-review check for subscribe(callback) patterns after migrating from RxJS
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
- Source must be a Function: ${String(source)}
- Returned cleanup function which cannot be called: ${String(c
- RelayObservable: Expected pollInterval to be positive, got:
- BabelPluginRelay: Expected plugin context to include "types"
- BabelPluginRelay: Expected exactly one definition per graphq
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/121af9ea80016bdf.
Report an issue: GitHub.