pmndrs/react-three-fiber · error · Error

Timed out after ${timeout}ms.

Error message

Timed out after ${timeout}ms.

What it means

This error is thrown by react-three-test-renderer's waitFor helper when the provided callback still returns a falsy value after the configured timeout has elapsed. waitFor polls callback() (with an optional interval between checks) until it returns truthy, or null/undefined which is treated as 'stop waiting'. Its purpose is to fail fast on asynchronous rendering that never completes within the test's time budget.

Source

Thrown at packages/test-renderer/src/helpers/waitFor.ts:19

import { act } from 'react'

export interface WaitOptions {
  interval?: number
  timeout?: number
}

export async function waitFor(
  callback: () => boolean | void,
  { interval = 50, timeout = 5000 }: WaitOptions = {},
): Promise<void> {
  await act(async () => {
    const start = performance.now()

    while (true) {
      const result = callback()
      if (result || result == null) break
      if (interval) await new Promise((resolve) => setTimeout(resolve, interval))
      if (timeout && performance.now() - start >= timeout) throw new Error(`Timed out after ${timeout}ms.`)
    }
  })
}

View on GitHub (pinned to ff3899dbf4)

Solutions

  1. Verify the condition is actually reachable — run the same interaction/assertion synchronously to confirm what state waitFor is polling.
  2. If the awaited update legitimately takes longer (debounce, suspense load, animation frames), increase the timeout (and optionally interval) passed to waitFor.
  3. If a resource never resolves, mock the asset/data load (mock loader, fixture data) so the subtree finishes rendering.
  4. Ensure the callback returns something truthy on success; note that returning null/undefined makes waitFor exit immediately without error, so return an actual boolean/instance.

Example fix

// before
await tree.waitFor(() => tree.findAll(specificMesh).length > 0) // hangs -> Timed out after 5000ms.

// after
await tree.waitFor(() => tree.findAll(specificMesh).length > 0, { timeout: 10000, interval: 100 })
// or trigger the state that mounts the mesh before waiting:
act(() => { setIsReady(true) })
Defensive patterns

Strategy: retry

Validate before calling

// Make the condition self-evidently satisfiable before waiting
const found = tree.findAll(target)
if (found.length === 0 && !willEverMount(target)) {
  // skip waitFor — nothing to wait for
}

Type guard

null

Try / catch

try {
  await tree.waitFor(() => tree.findAll(target).length > 0, { timeout: 5000, interval: 50 })
} catch (e) {
  if (e instanceof Error && /Timed out after/.test(e.message)) {
    // dump current tree to debug what actually rendered
    console.log(JSON.stringify(tree.toTree(), null, 2))
  } else throw e
}

Prevention

When it happens

Trigger: Calling waitFor(() => someCondition, { timeout }) where someCondition stays falsy: e.g. waiting for a mesh that never mounts, a Suspense resource that never resolves, an animation/state update that never happens, or passing a callback whose truthiness check is wrong (returns 0, '', false indefinitely).

Common situations: Async data fetching that hangs in tests (unmocked fetch/asset loader), components gated behind a state flag that the test never triggers, timeouts that are too short for a debounced/animated update, or awaiting an event that the renderer never fires because the scene is static.

Understand the failure class

Related errors


AI-assisted analysis of pmndrs/react-three-fiber@ff3899dbf4 (2026-08-28). Data as JSON: /api/errors/c0e4707ebeedb543. Report an issue: GitHub.