facebook/relay · error

Source must be a Function: ${String(source)}

Error message

Source must be a Function: ${String(source)}

What it means

RelayObservable's constructor requires its `source` argument to be a function (the source's start callback). This dev-mode check throws immediately when you construct RelayObservable directly with a non-function (or falsy) source. The public API is `RelayObservable.create(source)`.

Source

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

 * synchronously, avoiding any UI jitter, while providing a compositional API,
 * which simplifies logic and prevents mishandling of errors compared to
 * the direct use of callback functions.
 *
 * ESObservable: https://github.com/tc39/proposal-observable
 */
class RelayObservable<out T> implements Subscribable<T> {
  readonly _source: Source<T>;

  static create<V>(source: Source<V>): RelayObservable<V> {
    return new RelayObservable(source as any);
  }

  // Use RelayObservable.create()
  constructor(source: empty): void {
    if (__DEV__) {
      // Early runtime errors for ill-formed sources.
      if (!source || typeof source !== 'function') {
        throw new Error('Source must be a Function: ' + String(source));
      }
    }
    (this as any)._source = source;
  }

  /**
   * When an emitted error event is not handled by an Observer, it is reported
   * to the host environment (what the ESObservable spec refers to as
   * "HostReportErrors()").
   *
   * The default implementation in development rethrows thrown errors, and
   * logs emitted error events to the console, while in production does nothing
   * (swallowing unhandled errors).
   *
   * Called during application initialization, this method allows
   * application-specific handling of unhandled errors. Allowing, for example,
   * integration with error logging or developer tools.
   *

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Use the static factory: `RelayObservable.create(sourceFunction)` instead of `new RelayObservable(...)`
  2. Wrap the value: for a promise use `create(env => promise.then(...))` or convert with an existing adapter (`fromPromise`, `fromAsyncIterable`)
  3. Ensure the variable passed is actually the source function and not the result of invoking it

Example fix

// before
new RelayObservable(promise);
// after
RelayObservable.create(sink => {
  promise.then(v => sink.next(v), e => sink.error(e));
});
Defensive patterns

Strategy: validation

Validate before calling

function safeCreateObservable(source) {
  if (typeof source !== 'function') {
    throw new TypeError('RelayObservable.create requires a source function');
  }
  return RelayObservable.create(source);
}

Type guard

function isObservableSource(source: unknown): source is (sink: Sink<unknown>) => void | (() => void) | {unsubscribe: () => void} {
  return typeof source === 'function';
}

Try / catch

try {
  const obs = RelayObservable.create(source);
} catch (e) {
  if (e.message.startsWith('Source must be a Function')) {
    throw new TypeError('Pass a source function to RelayObservable.create, not ' + typeof source, {cause: e});
  }
  throw e;
}

Prevention

When it happens

Trigger: `new RelayObservable(notAFunction)` — passing an object, promise, array, or undefined/null directly to the constructor; calling `create` with a non-function value in __DEV__ builds.

Common situations: Porting code from other Observable libs (RxJS accepts objects/arrays as sources); forgetting to wrap a promise in a function; typos passing the wrong variable to the constructor.

Related errors


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