How to use three.js in React with React three fiber

Introduction

React Three-Fiber (often abbreviated as R3F) allows you to use Three.js in React applications to create and display 3D computer graphics for web browsers.

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

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

In this article, you’ll learn how to leverage react-three-fiber to add 3D graphics and animations to React applications using three.js.

Prerequisites

This article assumes familiarity with React (with hooks) and three.js. If unfamiliar, you can get started with React hooks and three.js quickly.

NOTE: A live version of this project is available on CodeSandbox. For the purposes of this tutorial, code splitting across multiple files will be emphasized to better illustrate how components are used. It should be noted that this method is not optimized for performance.

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

Explore react-three-fiber

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

Component-based scenarios: It allows you to write three.js objects in a declarative way, so you can build scenarios utilizing props, state, and hooks by creating reusable React components. Remember that you can basically write the entire directory of three.js objects with all their properties.

Built-in helpers: It provides some useful functions like raycaster, which allows you to access all useful pointer events like onClick, onPointerOver, onPointerOut, etc. on each grid. Just like any DOM element.

Hooks: It already provides a lot of hooks, such as useFrame, which allows us to attach functions to the raf loop (even override the default one), and useThree, from which we can get useful objects like renderers, scenes, cameras, etc.

Resizing: If you don’t need special custom resizing logic, it already handles camera changes for you, and the canvas resizes independently. You can even access the size of the viewport (the size of the rendered quad based on the 3D coordinates).

No dependencies: Since it is a renderer, it does not depend on the three.js version, so you are free to choose your favorite version.

Only re-render when needed: it works like any React component, it updates itself based on changes in dependencies (state, storage, etc.).

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

Step 1 – Setting Up Project

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

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

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 has been completed. With these few lines of code, you’ve created the canvas, the camera (a perspective camera, but you can customize it), and the scene.

Step 2 – Add a group with a grid

Let’s compare two methods of how to create a basic mesh and add it to a group.

Here’s an example of defining a grid and adding it to a group using 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);

Here is the same grid using the 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, there is no need to add the scene as it is added automatically since it belongs to the child object of the canvas.

Just pass the required parameters as args property, then you can set all other properties as props.

Step 3 – Create multiple cubes

Now suppose you want to create multiple cubes and add them to a group.

In plain JavaScript, you’d create a class (or not, depending on your preferred approach) to create and process them, push them into an array, etc.

With R3F, you can add an array of components to the content returned by the function, 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 – Make cube animation

Now let’s animate them. react three-fiber gives you a nice way to wire logic into raf loops 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 – Handle stateful properties

Now, let’s change some properties on hover or click. You don’t need to set up any raycasters as it is already defined for you. Since you’re using React, you can take advantage of 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 animation by changing the state of the cube from “active” to “inactive” based on the user’s click. As before, you can use 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 – Add ambient light and point light

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

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

Step 7 – Add rotation

Finally, let’s add our custom logic to the render loop.

Let’s add a rotation to all the cube’s container groups. You can 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;
});

Summary

Since I discovered react three fiber, I’ve used it for all my react three.js projects, from the simplest demos to the most complex. I strongly recommend that you read all the documentation online and get an overview of all the features, as we’ve only covered some of them here.

Since R3F is brought to you by Paul Henschel, it is fully compatible with react spring. This means you can use react spring to animate all three.js, accessing elements directly, which is completely shocking to be honest.

BTW, if you’re used to using three.js, you’ll have to change your interactions and creations a bit. It has a learning curve, and if you want to maintain optimal performance, such as using shared materials, cloning meshes, etc., you will need some time to change the method, but in my experience, it is definitely worth the investment of time.