react-three-fiber: a React renderer for Three.js, and the version pairing that decides your upgrade
🇨🇭 A React renderer for Three.js
At a glance
- What is it?
- react-three-fiber lets you describe a Three.js scene as JSX components that hold state and join the render loop. The README claims no overhead and no limitations, but it also states a hard React major-version pairing that governs every upgrade.
- Who is it for?
- Adopt react-three-fiber if your application already lives in React and the 3D view has to share state with the rest of the UI; skip it if the scene is a standalone viewer with no React around it, since you would be adding a renderer and a version-pairing obligation for nothing.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem react-three-fiber solves: scene state that lives in React
Three.js builds a scene graph through imperative calls. You construct a mesh, attach geometry and material, add it to the scene, and later mutate transforms yourself. That model works well when the scene is the whole program, and it becomes awkward when the scene is one panel inside a larger interface. The README frames the alternative as building a scene declaratively with re-usable, self-contained components that react to state, are readily interactive and can participate in React's ecosystem. That is the audience: teams that already have React state, routing, and component boundaries, and want a 3D view to be one more component rather than a separate subsystem with its own lifecycle. The example in the README is a Box component that keeps hovered and clicked in useState, returns mesh, boxGeometry and meshStandardMaterial as JSX, and subscribes to the frame loop with useFrame. Nothing in that component reaches for a renderer handle. The scene graph is a function of React state.
How the renderer maps JSX to Three.js objects
react-three-fiber is a React renderer in the same category as react-dom and react-native. The README states the mapping directly: it merely expresses Threejs in JSX, and <mesh /> dynamically turns into new THREE.Mesh(). That sentence is the architecture in miniature. Lowercase JSX elements become Three.js constructors; props become properties on the constructed object; children become scene graph descendants. Because the mapping is dynamic rather than a fixed catalogue, the README argues that a new Three.js version's added, removed, or changed features are available instantly without waiting on this library to publish an update. The render loop is opt-in per component. useFrame takes a callback with state and delta, and the README's example mutates ref.current.rotation.x by delta each frame. That is the escape hatch from declarative updates to per-frame mutation, and it is where most animation code will end up. Interaction is handled through props on the element itself: onClick, onPointerOver, onPointerOut appear directly on mesh in the example, with the event handlers flipping React state that in turn changes the scale and material color props.
Install commands and the Canvas entry point
The install line in the README is npm install three @types/three @react-three/fiber. Note that three is a peer you install yourself, not something the package pulls in silently, and the TypeScript types come from @types/three rather than from the renderer. The browser entry point is Canvas, imported from @react-three/fiber, rendered through createRoot from react-dom/client. In the README example the Canvas wraps lights and two Box instances, and the lights are ordinary JSX elements with Three.js-flavoured props: ambientLight takes intensity, spotLight takes position, angle, penumbra, decay and intensity, pointLight takes position and decay. The TypeScript variant imports ThreeElements from @react-three/fiber and types the component props as ThreeElements['mesh'], which is the pattern to copy if you want prop names checked against the underlying Three.js class. For React Native the import path changes to @react-three/fiber/native, and the README's Expo instructions are npm install three @react-three/fiber@beta react@rc followed by expo start. That example also notes that useLoader and abstractions such as useGLTF and useTexture may need Metro told about your asset types, via a metro.config.js resolver block listing sourceExts including tsx and cjs and assetExts including glb and png.
The React major-version pairing is the real upgrade constraint
The README carries a warning that matters more than anything else on the page: three-fiber is a React renderer, it must pair with a major version of React, just like react-dom, react-native, etc. It then gives the mapping explicitly. @react-three/fiber@8 pairs with react@18, and @react-three/fiber@9 pairs with react@19. This is not a soft compatibility note. A React renderer hooks into React's reconciler internals, so a React major bump is a renderer major bump, and the two move together. The practical consequence is that adopting this library couples your React upgrade schedule to the renderer's release schedule. If you are on react@18, you are on the 8.x line. If you move to react@19, you move to 9.x. The recent release list shows v9.7.0 published in July 2026 and a 10.0.0 alpha line running through September 2026, so the 9.x line was still receiving stable releases while 10.x remained in alpha. Anyone planning an upgrade should read the pairing table as a constraint on the whole application, not as a per-package detail.
Where the README's claims outrun what a reader can verify
The README answers three self-posed questions with flat assertions. Does it have limitations? None. Everything that works in Threejs will work here without exception. Is it slower than plain Threejs? No. There is no overhead. Components render outside of React. It outperforms Threejs in scale due to React's scheduling abilities. Treat those as marketing until you measure your own case. The claim that components render outside of React is the interesting one, because it is partly a description of how the reconciler batches work, and partly a promise about frame timing that depends entirely on what your useFrame callbacks do. A scene whose per-frame work is heavy will not be rescued by React's scheduling. The statement that everything works without exception also sits oddly beside the version pairing warning on the same page: the renderer is version-locked to React, so the surface is not unbounded. There is no benchmark, no profiling methodology, and no listed limitation anywhere in the README. That absence is itself information when you are deciding whether to trust the performance claim.
When plain Three.js is the better tool
The honest comparison is not against another React renderer but against Three.js used directly. Plain Three.js gives you a scene graph you construct and mutate in ordinary imperative code, with no reconciler in the middle and no React major-version pairing to track. If your 3D content is a self-contained viewer, a configurator embedded in a mostly static page, or a canvas with no shared application state, the declarative layer buys you little and costs you a dependency plus the pairing constraint. The difference in approach is concrete: in plain Three.js you write new THREE.Mesh() and manage its lifetime; in react-three-fiber you write <mesh /> and let the renderer construct and dispose it as React mounts and unmounts the component. That trade is worth taking when component boundaries, prop-driven updates, and shared state are already how your application is organised. It is a poor trade when the scene has no React state to react to. The README's own framing supports this reading: it sells re-usable, self-contained components that participate in React's ecosystem, which is a benefit only if there is an ecosystem to participate in.
Licence and what maintenance actually costs you
The repository is MIT licensed, which permits commercial use, modification, and redistribution provided the copyright notice and permission notice are preserved. That is the standard permissive position and it does not create obligations on your own source. The maintenance cost sits elsewhere. Three.js ships frequently, and the README's argument is that this library does not need to chase it because the JSX mapping is dynamic. That argument is plausible for new constructors and properties, and it is not a guarantee about breaking changes in Three.js itself, which will surface in your code rather than in the renderer. The second cost is the React pairing. Every React major upgrade forces a matching renderer major, and the 10.x line was still in alpha as of the latest release listed. The third cost is the ecosystem around the renderer: the React Native path pulls in Expo tooling, a Metro config edit for asset extensions, and in the README's own example a beta renderer tag paired with a React release candidate. Those are moving parts you inherit. None of this is unusual for a React renderer, but it should be priced in before adoption rather than after.
Editorial conclusion
Adopt react-three-fiber if your application already lives in React and the 3D view has to share state with the rest of the UI; skip it if the scene is a standalone viewer with no React around it, since you would be adding a renderer and a version-pairing obligation for nothing. Before committing, verify the exact pairing for your React major (@react-three/fiber@8 with react@18, @react-three/fiber@9 with react@19), and check whether the 10.x line is still published as alpha, because the newest release listed is v10.0.0-alpha.5 rather than a stable tag.
Community notes