Needle Engine + React
Want interactive 3D in your React app? Needle Engine ships as a standard web component, so it drops straight into JSX — no React-specific wrapper required. Install it from npm, render <needle-engine>, and you get an optimized, compressed 3D scene with camera controls, lighting, physics, XR and networking built in.
Start from a template
Scaffold a ready-to-run project with the create-needle CLI:
npx create-needle my-app -t reactOr explore the full React sample (source). Prefer plain HTML/JS? engine.needle.tools/new opens a vanilla Vite starter in StackBlitz.
Quick Start
1. Install Needle Engine in your React project (Vite, Create React App, Next.js, …):
npm i @needle-tools/engine2. Import the engine once (a side-effect import registers the <needle-engine> custom element) and render it in JSX:
import '@needle-tools/engine';
export default function Scene() {
return (
<needle-engine
src="https://cloud.needle.tools/-/assets/.../scene.glb"
camera-controls
background-color="transparent"
environment-image="studio"
contact-shadows
></needle-engine>
);
}That's it — the component bundles with your app for an optimized production build. See the web component reference for the full list of attributes.
TypeScript
No manual declaration needed — <needle-engine> is typed in JSX automatically. The package entry references its own JSX declarations, so just importing the engine registers the element types for React (and Preact, SolidJS, Svelte, and vanilla TS):
import '@needle-tools/engine';
// <needle-engine src="…"> is now type-checked in JSXAttributes are typed by the exported NeedleEngineAttributes interface. Import it to build a small typed wrapper, so props get autocomplete and type-checking at the call site:
import '@needle-tools/engine';
import type { NeedleEngineAttributes } from '@needle-tools/engine';
// Every attribute is optional — pass through whichever you need.
type SceneProps = Partial<NeedleEngineAttributes>;
export function Scene(props: SceneProps) {
return <needle-engine {...props}></needle-engine>;
}// Usage — fully type-checked and autocompleted:
<Scene
src="https://cloud.needle.tools/-/assets/.../scene.glb"
camera-controls="true"
environment-image="studio"
contact-shadows="true"
/>React 18 vs 19
React 19 has first-class custom-element support and forwards props as attributes/properties correctly. On React ≤ 18, string/number/boolean values are passed as attributes — all that the <needle-engine src camera-controls> markup above needs — but complex values (objects, functions) are not; pass those imperatively (see Accessing the scene).
Accessing the scene from React
Needle exposes global lifecycle hooks — onStart, onInitialized, onUpdate (plus onBeforeRender, onClear, …). Each receives the Context and returns an unsubscribe function, which maps cleanly onto a React useEffect. This is the recommended way to reach the scene from React — prefer it over reading the element imperatively.
In a single component
Subscribe in an effect and keep the context in state, so the component re-renders once the scene is ready:
import { useEffect, useState } from 'react';
import { onInitialized } from '@needle-tools/engine';
import '@needle-tools/engine';
export default function Scene() {
const [context, setContext] = useState(null);
useEffect(() => {
// fires once the scene is created and its first content has loaded
// (also re-fires if `src` changes). Returning the unsubscribe fn lets
// React clean up — and makes StrictMode's double-invoke safe.
return onInitialized(ctx => setContext(ctx));
}, []);
useEffect(() => {
if (!context) return;
console.log('Scene ready', context.scene);
// query Needle components, add three.js objects, drive animations…
}, [context]);
return <needle-engine src="path/to/your.glb"></needle-engine>;
}Use onStart instead of onInitialized to run before the first content finishes loading, or onUpdate for per-frame logic — same pattern, just return the unsubscribe from the effect.
Across your app (store)
If several components need the scene, subscribe once and put the context in a store (Zustand, Jotai, or a React Context) instead of threading props:
// store.js
import { create } from 'zustand';
export const useNeedle = create(set => ({
context: null,
setContext: ctx => set({ context: ctx }),
}));
// mounted once (e.g. in your <App>)
import { useEffect } from 'react';
import { onInitialized } from '@needle-tools/engine';
import { useNeedle } from './store';
useEffect(() => onInitialized(useNeedle.getState().setContext), []);Any component can then read const context = useNeedle(s => s.context).
Which approach?
- State + hook subscription (above) — one component that should re-render when the scene is ready. Recommended default.
- Store — many components across the tree need the scene. Subscribe once, share via the store.
useRefon the element — for imperative access from event handlers only.await ref.current.getContext()resolves with theContext(or read the syncref.current.context, which isundefineduntil ready). A ref won't trigger a re-render, so don't use it to drive "is the scene ready?" UI.- Mounting late? If a component appears after the scene is already running, the global hooks may not re-fire — read
Context.Currentas a fallback. - Multiple
<needle-engine>elements? The global hooks fire for each context — use thectxargument to tell them apart, or scope via the element ref.
For writing your own interactive logic as components, see Scripting & creating components.
Two ways to use React with Needle
| Approach | When to use |
|---|---|
<needle-engine> web component in JSX (above) | You author scenes in Unity/Blender (or load a .glb) and embed them in a React UI. Recommended for most apps. |
| react-three-fiber interop | You already build your scene with react-three-fiber and want Needle features alongside it. ⚡ Experimental — see the frameworks overview. |
Server-side rendering (Next.js)
<needle-engine> is a client-side component. In Next.js, render it only on the client (e.g. a 'use client' component, or next/dynamic with { ssr: false }). When your framework serves exported assets from a different path than they're written to, set baseUrl in needle.config.json. A starting point is the Next.js example project.
Next Steps
- Frameworks, bundlers & HTML overview — all supported stacks and the support matrix
- Web component attributes — every
<needle-engine>option - Write components & scripting — add your own interactivity
- Deploy to Needle Cloud — one-command hosting with a shareable URL