Tutorial

How To Use three.js in React with react-three-fiber

React
This tutorial is out of date and no longer maintained.

Introduction

react-three-fiber (often shortened to R3F) allows you to use three.js in your React applications to create and display 3D computer graphics for web browsers.

If you’re familiar with working with React and you want to create a three.js experience, the standard approach can be painful to scale. react-three-fiber addresses this pain point, by providing three.js conventions that work within the React ecosystem.

Some examples of projects created with react-three-fiber include Let Girls Dream, Gucci’s 1955 Horse Bit Bag and Gucci’s 24 Hour Ace.

In this article, you will learn how to add 3D graphics and animations to your React apps using three.js by leveraging the react-three-fiber library.

Prerequisites

This article supposes you are familiar with React (with hooks) and three.js. If not, you can get a quick start on React hooks here and on three.js here.

Note: A live version of this project is available on CodeSandbox. For the purposes of this tutorial, there will be an emphasis on code splitting in multiple files to better illustrate working with components. It is important to note that this approach is not performance-optimized.

This tutorial assumes you have a React app with three, react-three-fiber, as lodash as dependencies.

Exploring react-three-fiber

Here are some reasons to consider react-three-fiber for your next project:

  • Component-based Scene: It allows you to write three.js objects in a declarative way, so you can build up your scene by creating re-usable React components, leveraging props, states and hooks. Keep in mind that you can write essentially the entire three.js object catalogue and all its properties.
  • Built-in helpers: It’s delivered with some useful functions like raycaster and it gives you access on each mesh to all the useful pointer-events like onClick, onPointerOver, onPointerOut, and more. Exactly like any DOM element.
  • Hooks: It comes with a lot of hooks already, like useFrame that allows us to attach functions into the raf loop (or even override the default one), and useThree from where we can get useful objects like renderer, scene, camera, and more.
  • Resize: If you don’t need special custom resize logic, it already handles the camera changes for you, and the canvas resizes itself independently. You can even get access to the size of the viewport (the size of the quad you are rendering based on 3D coordinates).
  • Dependency-free: Since it’s a renderer, it doesn’t rely on the three.js version, so you are free to choose your preferred version.
  • Re-render only when needed: It works as any React component, it updates itself on a dependency change (state, store, etc.).

Next, let’s use react-three-fiber.

Step 1 — Setting Up the Project

First, you will need to define the Canvas component. Everything inside of it will be added to the main scene (defined by react-three-fiber).

Open the /src/App.js file in your code editor. Replace the code with the following new lines:

src/App.js
import { Canvas } from "react-three-fiber";

function App() {
  return (
    <div className="App">
      <Canvas>
        <!--
          <Children/> // any three.js object (mesh, group, etc.)
          <Children/> // any three.js object (mesh, group, etc.)
        -->
      </Canvas>
    </div>
  );
}

export default App;

The first step is done. With these few lines of code, you’ve already created the canvas, the camera (a perspective one but you can customize it), and the scene.

Step 2 — Adding a Group with a mesh

Let’s compare the two approaches on how to create a basic mesh and add it into a group.

Here is an example of defining a mesh and adding it to a group with JavaScript:

const group = new Group();
const geo = new BoxBufferGeometry(2,2,2);
const mat = new MeshStandardMaterial({color: 0x1fbeca});
const mesh = new Mesh(geo, mat);
group.position.set(0,0.1,0.1);
group.add(mesh);
scene.add(group);

And here is the same mesh using a declarative approach:

<group position={[0,0.1,0.1]}>
  <mesh>
    <boxBufferGeometry attach="geometry" args={[0.047, 0.5, 0.29]} />
    <meshStandardMaterial attach="material" color={0xf95b3c} />
  </mesh>
</group>

In the declarative approach, it is not necessary to add the scene because it’s automatically added since it’s a child of the Canvas.

You just need to pass the required arguments as args property and then you can set all the other properties as props.

Step 3 — Creating Multiple Cubes

Suppose now that you want to create multiple cubes and add them to the group.

In plain vanilla JavaScript, you would need to create a class (or not, depends on your preferred approach) to create and handle them, and push them into an array, and so on.

With R3F you can add an array of components into what the function returns, as any DOM-element. You can even pass a prop and apply it internally.

export default () => {
  const nodesCubes = map(new Array(30), (el, i) => {
    return <Cube key={i} prop1={..} prop2={...}/>;
  });

  return (
    <group>
      { nodesCubes }
    </group>
  );
};

Step 4 — Animating Cubes

Now let’s animate them. react-three-fiber provides you a great way to attach your logic into the raf loop, using the useFrame hook.

// ...
import {useFrame} from 'react-three-fiber'
// ...

const mesh = useRef()

useFrame( ({gl,scene,camera....}) => {
  mesh.current.rotation.y += 0.1
})

Step 5 — Handling Properties with States

Now let’s change some properties on hover or on click. You don’t need to set any raycaster because it’s already defined for you. Since you are using React you can leverage the useState hook.

//...
const mesh = useRef()
const [isHovered, setIsHovered] = useState(false);
const color = isHovered ? 0xe5d54d : 0xf95b3c;

const onHover = useCallback((e, value) => {
    e.stopPropagation(); // stop it at the first intersection
    setIsHovered(value);
  }, [setIsHovered]);
//...

<mesh 
  ref={mesh}
  position={position}
  onPointerOver={e => onHover(e, true)}
  onPointerOut={e => onHover(e, false)}
>
  <boxBufferGeometry attach="geometry" args={[0.047, 0.5, 0.29]} />
  <meshStandardMaterial color={color} attach="material" />
</mesh>

Now let’s change the color and the animation by modifying the state of the cube from “active” to “inactive” based on the user’s click. As before, you can play with state and use the built-in function onClick.

//...
const [isHovered, setIsHovered] = useState(false);
const [isActive, setIsActive] = useState(false);
const color = isHovered ? 0xe5d54d : (isActive ? 0xf7e7e5 : 0xf95b3c);

  const onHover = useCallback((e, value) => {
    e.stopPropagation();
    setIsHovered(value);
  }, [setIsHovered]);

  const onClick = useCallback(
    e => {
      e.stopPropagation();
      setIsActive(v => !v);
    },
    [setIsActive]
  );

  // raf loop
  useFrame(() => {
    mesh.current.rotation.y += 0.01 * timeMod;
    if (isActiveRef.current) { // a ref is needed because useFrame creates a "closure" on the state
      time.current += 0.03;
      mesh.current.position.y = position[1] + Math.sin(time.current) * 0.4;
    }
  });
//...

return (
  <mesh
    ref={mesh}
    position={position}
    onClick={e => onClick(e)}
    onPointerOver={e => onHover(e, true)}
    onPointerOut={e => onHover(e, false)}
  >
    <boxBufferGeometry attach="geometry" args={[0.047, 0.5, 0.29]} />
    <meshStandardMaterial color={color} attach="material" />
  </mesh>
);

Step 6 — Adding ambientlight and pointLight

As before, if you want to add some lights you will need to create a function (or component) and add it into the scene graph:

return (
  <>
    <ambientLight intensity={0.9} />
    <pointLight intensity={1.12} position={[0, 0, 0]} />
  </>
)

Step 7 — Adding a rotation

Finally, let’s add custom logic into the render-loop.

Let’s add a rotation on the container group of all the cubes. You can just simply go to the group component, use the useFrame hook and set the rotation there, just like before.

 useFrame(() => {
  group.current.rotation.y += 0.005;
});

Conclusion

Since I’ve discovered react-three-fiber, I’ve used it for all my React three.js projects, from the simplest demo to the most complex one. I strongly suggest you read all the documentation online and overview all the features because here we just covered some of them.

Since R3F is brought to you by Paul Henschel it’s totally compatible with react-spring. This means you can use react-spring to animate all your three.js stuff, accessing directly to the element, and to be honest it’s totally mind-blowing.

By the way, if you are used to working with three.js, you’ll have to switch a little how you interact and create stuff. It has a learning curve and you’ll need some time to change the approach if you want to keep performance at its best, like for example using a shared material, cloning a mesh, etc, but based on my experience it’s absolutely worth the time invested.

Creative Commons License