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: