solidjs/solid · error · Error

Dispose method must be an explicit argument to createRoot fu

Error message

Dispose method must be an explicit argument to createRoot function

What it means

createRoot(fn) called without an owner (unowned) hands fn a dispose callback that, in dev builds, throws when invoked. Unowned roots have no owner to attach disposal to, so Solid makes the useless dispose loud instead of silently doing nothing. In production builds the dispose is simply a no-op.

Source

Thrown at packages/solid/src/reactive/signal.ts:169

  const listener = Listener,
    owner = Owner,
    unowned = fn.length === 0,
    current = detachedOwner === undefined ? owner : detachedOwner,
    root: Owner = unowned
      ? IS_DEV
        ? { owned: null, cleanups: null, context: null, owner: null }
        : UNOWNED
      : {
          owned: null,
          cleanups: null,
          context: current ? current.context : null,
          owner: current
        },
    updateFn = unowned
      ? IS_DEV
        ? () =>
            fn(() => {
              throw new Error("Dispose method must be an explicit argument to createRoot function");
            })
        : fn
      : () => fn(() => untrack(() => cleanNode(root)));

  if (IS_DEV) DevHooks.afterCreateOwner && DevHooks.afterCreateOwner(root);

  Owner = root;
  Listener = null;

  try {
    return runUpdates(updateFn as () => T, true)!;
  } finally {
    Listener = listener;
    Owner = owner;
  }
}

export type Accessor<T> = () => T;

View on GitHub (pinned to f47845f9cc)

Solutions

  1. Pass your own dispose as the second argument: createRoot(disposer => { ... }, myDispose)
  2. Or run the code inside an existing owner (e.g. inside createRoot created by render())
  3. Or never call the dispose callback of an unowned root and manage cleanup via onCleanup elsewhere

Example fix

// before
createRoot((dispose) => {
  createEffect(() => console.log(state()));
  setTimeout(dispose, 1000); // throws in dev
});

// after
const disposer = () => { /* cleanup */ };
createRoot((dispose) => {
  createEffect(() => console.log(state()));
  // don't call dispose; cleanup handled by disposer
}, disposer);
Defensive patterns

Strategy: validation

Validate before calling

let owner: Owner | null = null;
createRoot((dispose) => {
  owner = getOwner();
  // store dispose only if you provided your own disposer (2nd arg)
}, () => { /* my dispose */ });

Try / catch

try { dispose(); } catch (e) { if (/explicit argument/.test(String(e))) { /* unowned root: no-op intended */ } else throw e; }

Prevention

When it happens

Trigger: Calling createRoot(fn) outside of any reactive owner and then invoking the dispose argument passed to fn (e.g. const [dispose] = createRoot(d => ...); dispose()). Common in top-level module code or tests without createRoot's second argument.

Common situations: Setting up module-scope state or web-component internals without an owner; upgrading Solid versions where disposal semantics changed; test setup calling dispose manually.

Related errors


AI-assisted analysis of solidjs/solid@f47845f9cc (2026-08-27). Data as JSON: /api/errors/d87ddbedcfb2cd5c. Report an issue: GitHub.