pmndrs/react-three-fiber · error · Error
R3F: Hooks can only be used within the Canvas component!
Error message
R3F: Hooks can only be used within the Canvas component!
What it means
All react-three-fiber hooks (useThree, useFrame, useStore, etc.) read the Canvas's Zustand store from React context. If a hook runs outside a <Canvas> subtree, the context value is undefined and R3F throws this guard error immediately. The check protects against silently broken hooks that would otherwise return undefined state and crash later in confusing ways.
Source
Thrown at packages/fiber/src/core/hooks.tsx:26
/**
* Exposes an object's {@link Instance}.
* @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useInstanceHandle
*
* **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
*/
export function useInstanceHandle<T>(ref: React.RefObject<T>): React.RefObject<Instance<T>> {
const instance = React.useRef<Instance>(null!)
React.useImperativeHandle(instance, () => (ref.current as unknown as Instance<T>['object']).__r3f!, [ref])
return instance
}
/**
* Returns the R3F Canvas' Zustand store. Useful for [transient updates](https://github.com/pmndrs/zustand#transient-updates-for-often-occurring-state-changes).
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usestore
*/
export function useStore(): RootStore {
const store = React.useContext(context)
if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!')
return store
}
/**
* Accesses R3F's internal state, containing renderer, canvas, scene, etc.
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree
*/
export function useThree<T = RootState>(
selector: (state: RootState) => T = (state) => state as unknown as T,
equalityFn?: <T>(state: T, newState: T) => boolean,
): T {
return useStore()(selector, equalityFn)
}
/**
* Executes a callback before render in a shared frame loop.
* Can order effects with render priority or manually render with a positive priority.
* @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useframeView on GitHub (pinned to ff3899dbf4)
Solutions
- Move the component using R3F hooks inside the <Canvas> element's children: <Canvas><MyScene /></Canvas>.
- If the component must live outside Canvas (e.g. DOM UI), pass the needed state down from a child inside Canvas, or use createPortal/Html-style patterns instead of the hooks.
- In tests, wrap the component in the test-renderer's Canvas equivalent (react-three-test-renderer's <Canvas> or createRoot(...).render) rather than @testing-library's DOM render.
- Double-check conditional rendering: a component that sometimes renders inside Canvas and sometimes not will throw only on the 'not' path.
Example fix
// before
function App() {
const { camera } = useThree() // Error: hooks can only be used within the Canvas
return <><DomUi /><Scene /></>
}
// after
function App() {
return (
<Canvas>
<Rig /> {/* useThree/useFrame live here */}
<Scene />
</Canvas>
)
} Defensive patterns
Strategy: type-guard
Validate before calling
null
Type guard
import { isInsideCanvas } from './guards' // hypothetical; practically: check via context consumer
// Practical guard: split components so R3F hooks only exist in Canvas children
// e.g. keep useFrame/useThree in <SceneContents /> rendered inside <Canvas> Try / catch
try {
const state = useThree() // note: hooks must not be called conditionally
} catch (e) {
// Not viable: never wrap hook calls in try/catch; restructure the tree instead
throw e
} Prevention
- Keep a rule: any component using useThree/useFrame/useStore is only ever rendered as a descendant of <Canvas>.
- Use ESLint eslint-plugin-react-hooks to catch conditional hook usage that can escape the Canvas subtree.
- In Storybook/tests, use the R3F Canvas (or react-three-test-renderer) as the render root.
When it happens
Trigger: Calling useThree(), useFrame(), useStore(), or any hook built on them (useLoader results access, events, raycast helpers) in a component rendered as a normal React child instead of inside <Canvas>...</Canvas>. Also happens when a component reads the store during module-level code or in a plain ReactDOM.render tree.
Common situations: Rendering an R3F scene component via ReactDOM into a DOM root (e.g. in Storybook or a test) without the Canvas wrapper; extracting a shared hook into a file used by both DOM UI and 3D components; hooks placed in a parent of <Canvas> rather than a child; HMR or refactoring accidentally moving a component outside the Canvas subtree.
Related errors
- `${prefix} ${msg}`
- Timed out after ${timeout}ms.
- R3F: ${name} is not part of the THREE namespace! Did you for
- R3F: Primitives without 'object' are invalid!
- R3F: The args prop must be an array!
AI-assisted analysis of pmndrs/react-three-fiber@ff3899dbf4 (2026-08-28).
Data as JSON: /api/errors/cba33e335559d592.
Report an issue: GitHub.