solidjs/solid · warning

cleanups created outside a `createRoot` or `render` will nev

Error message

cleanups created outside a `createRoot` or `render` will never be run

What it means

onCleanup registers cleanup on the current Owner. When Owner is null there is no disposal lifecycle, so the cleanup would never run; Solid emits this dev-only console warning rather than dropping the callback silently. The function is still returned, but its cleanup intent is void.

Source

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

 * @param fn an effect that should run only once on mount
 *
 * @description https://docs.solidjs.com/reference/lifecycle/on-mount
 */
export function onMount(fn: () => void) {
  createEffect(() => untrack(fn));
}

/**
 * Runs an effect once before the reactive scope is disposed
 * @param fn an effect that should run only once on cleanup
 *
 * @returns the same {@link fn} function that was passed in
 *
 * @description https://docs.solidjs.com/reference/lifecycle/on-cleanup
 */
export function onCleanup<T extends () => any>(fn: T): T {
  if (Owner === null)
    IS_DEV && console.warn("cleanups created outside a `createRoot` or `render` will never be run");
  else if (Owner.cleanups === null) Owner.cleanups = [fn];
  else Owner.cleanups.push(fn);
  return fn;
}

/**
 * Runs an effect whenever an error is thrown within the context of the child scopes
 * @param fn boundary for the error
 * @param handler an error handler that receives the error
 *
 * * If the error is thrown again inside the error handler, it will trigger the next available parent handler
 *
 * @description https://docs.solidjs.com/reference/reactive-utilities/catch-error
 */
export function catchError<T>(fn: () => T, handler: (err: Error) => void) {
  ERROR || (ERROR = Symbol("error"));
  Owner = createComputation(undefined!, undefined, true);
  Owner.context = { ...Owner.context, [ERROR]: [handler] };

View on GitHub (pinned to f47845f9cc)

Solutions

  1. Move the onCleanup call inside a component body or a createRoot
  2. Manage cleanup manually with try/finally or explicit dispose functions in non-reactive code
  3. Use createRoot(dispose => ...) and call dispose yourself for standalone reactive islands

Example fix

// before
const t = setInterval(tick, 1000);
onCleanup(() => clearInterval(t)); // module scope: never runs

// after
const App = () => {
  const t = setInterval(tick, 1000);
  onCleanup(() => clearInterval(t));
  return <Foo />;
};
Defensive patterns

Strategy: validation

Validate before calling

import { getOwner } from 'solid-js';
if (getOwner()) onCleanup(fn); else registerManualCleanup(fn);

Type guard

import { getOwner } from 'solid-js';
const insideOwner = (): boolean => getOwner() !== null;

Prevention

When it happens

Trigger: Calling onCleanup(fn) at module scope, inside top-level awaits outside the tree, or in code executed before render()/createRoot establishes an owner.

Common situations: Timer/interval setup in module-level code; web worker scripts sharing utilities with components; refactoring component logic into standalone functions losing the owner context.

Related errors


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