Three.js and React with react-three-fiber

Published

This week I got back into learning three.js and wanted to integrate with my favorite frontend UI library. The goal was to build a simple react app that changes the color of a cube. The requirements were to use React and Three.js together. I found react-three-fiber, a react renderer for three.js.

npm install three @react-three/fiber --save

That was all I needed to get started. I ended up using this live demo to start with.

Render one cube and break out Box component

 // src/components/Box.js

 import React, { useRef } from 'react'; 
 import { useFrame } from '@react-three/fiber'; 
  
 export const Box = props => { 
   // This reference will give us direct access to the mesh 
   const ref = useRef() 
   // Rotate mesh every frame, this is outside of React without overhead 
   useFrame(() => { 
     ref.current.rotation.x = ref.current.rotation.y += 0.01 
   }) 
   return ( 
     <mesh 
       {...props} 
       ref={ref}> 
       <boxGeometry args={[1, 1, 1]} /> 
       <meshStandardMaterial color='orange' /> 
     </mesh> 
   ) 
 } 

Look at that super handy useFrame hook for the rotation animation. Here’s what the App.js file is left with:

 // src/App.js

 import React from 'react'; 
 import { Canvas } from '@react-three/fiber'; 
 import { Box } from './components/Box'; 
  
 const App = () => { 
   return ( 
     <Canvas> 
       <ambientLight intensity={0.5} /> 
       <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} /> 
       <pointLight position={[-10, -10, -10]} /> 
       <Box position={[0, 0, 0]} /> 
     </Canvas> 
   ) 
 } 
  
 export default App; 

Now I have the one cube rendered and can build a menu for changing the cube color.

Create a menu component that changes a current cube color state

// src/components/Menu.js 

 import React from 'react'; 
  
 export const Menu = ({handleColorChange}) => { 
   return ( 
     <nav> 
       <ul> 
         <li> 
           <a href="/red" onClick={event => handleColorChange(event, 'red')}>Red</a> 
         </li> 
         <li> 
           <a href="/green" onClick={event => handleColorChange(event, 'green')}>Green</a> 
         </li> 
         <li> 
           <a href="/blue" onClick={event => handleColorChange(event, 'blue')}>Blue</a> 
         </li> 
       </ul> 
     </nav> 
   ) 
 } 

Then I imported the Menu component into App.js, added some state and the handleColorChange function.

 // src/App.js

 import React, { useState } from 'react'; 
 import { Canvas } from '@react-three/fiber'; 
 import { Box } from './components/Box'; 
 import { Menu } from './components/Menu'; 
  
 const App = () => { 
   const [currentColor, setCurrentColor] = useState('orange'); 
  
   const handleColorChange = (event, color) => { 
     event.preventDefault(); 
     setCurrentColor(color); 
   } 

   return ( 
     <div> 
       <Menu handleColorChange={handleColorChange} /> 
       <Canvas> 
         <ambientLight intensity={0.5} /> 
         <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} /> 
         <pointLight position={[-10, -10, -10]} /> 
         <Box position={[0, 0, 0]} /> 
       </Canvas> 
     </div> 
   ) 
 } 
  
 export default App; 

After I styled up the menu I refactored the Box.js component.

Refactor Box component to allow state change from menu

// src/App.js

...
 <Box currentColor={currentColor} position={[0, 0, 0]} /> 
...
// src/components/Box.js

...
 export const Box = ({currentColor}) => { 
   // This reference will give us direct access to the mesh 
   const ref = useRef() 
   // Rotate mesh every frame, this is outside of React without overhead 
   useFrame(() => { 
     ref.current.rotation.x = ref.current.rotation.y += 0.01 
   }) 
   return ( 
     <mesh 
       ref={ref}> 
       <boxGeometry args={[1, 1, 1]} /> 
       <meshStandardMaterial color={currentColor} /> 
     </mesh> 
   ) 
 } 
...

I created a repo for this project and branch with all this code so far. Here’s what it looks like: