Three Prompts: Rubiks Cube
Three Prompts: Rubiks Cube

Introduction
The Rubik's Cube has fascinated puzzle enthusiasts since its invention in 1974. This iconic 3×3×3 mechanical puzzle has sold over 450 million units worldwide, making it the best-selling puzzle game and toy in history. In this Three Prompts project, we'll create an interactive, browser-based Rubik's Cube that users can manipulate with both button controls and mouse/touch interactions.
Using React, Three.js, and a specialized Rubik's Cube library, we'll build a fully functional 3D cube visualization that responds to user input. The project demonstrates how quickly we can implement complex 3D interactions in the browser by leveraging existing libraries and React's component model.
The Three Prompts
Here are the exact prompts that were used to create this project:
Prompt 1
In @projects/rubiks-cube make a demo project showing a rubiks cube in the browser than works as expected
Prompt 2
Now make it so the cube can be rotated so you can see the other sides
Prompt 3
Uncaught TypeError: Cannot read properties of null (reading 'removeEventListener')
Building a 3D Rubik's Cube
Setting Up the Project
Our Rubik's Cube demo is built on a Vite + React foundation, which provides fast development and optimized production builds. The project structure is minimal but effective:
projects/rubiks-cube/
├─ package.json – dependencies and scripts
├─ src/
│ ├─ App.jsx – renders our cube component
│ ├─ RubiksCubeDemo.jsx – core implementation
│ └─ main.jsx – React entry point
└─ public/ – static assets
The key dependencies for this project are:
- React 19 for the UI framework
- Three.js for 3D rendering capabilities
@curtishughes/rubiks-cube
package which provides the cube implementation
The Core Component
The heart of our implementation is the RubiksCubeDemo
component. Let's examine how it works:
import { useEffect, useRef, useState } from 'react'
import RubiksCube, { materials } from '@curtishughes/rubiks-cube'
export default function RubiksCubeDemo() {
const canvasRef = useRef(null)
const [cube, setCube] = useState(null)
useEffect(() => {
const canvasEl = canvasRef.current
if (!canvasEl) return undefined
// Create cube instance once
const cubeInstance = new RubiksCube(canvasEl, materials.classic, 150)
setCube(cubeInstance)
// Pointer event handlers for rotation...
}, [])
// Button handlers and rendering...
}
The component uses React's useRef
to access the canvas element and useState
to store a reference to the cube instance. The useEffect
hook initializes the cube when the component mounts.
Face Rotation Controls
The first set of controls allows users to rotate individual faces of the cube using standard Rubik's Cube notation:
F/F'
- Front face clockwise/counterclockwiseR/R'
- Right face clockwise/counterclockwiseU/U'
- Upper face clockwise/counterclockwise
These controls are implemented through simple button clicks that call the corresponding methods on the cube instance:
function handleMove(move) {
if (!cube) return
switch (move) {
case 'F':
cube.F()
break
case "F'":
cube.F(false)
break
// Other cases...
}
}
Whole Cube Rotation
The second set of controls enables rotating the entire cube to view it from different angles:
- Button Controls: Using the
x
,y
, andz
axis rotations (and their counterclockwise variants) - Pointer Drag: Horizontal and vertical dragging to rotate around the y and x axes respectively
The drag implementation is particularly interesting:
const onPointerMove = (e) => {
if (!isDragging) return
const dx = e.clientX - startX
const dy = e.clientY - startY
// Horizontal drag rotates around Y axis
if (Math.abs(dx) > 30) {
if (dx > 0) cubeInstance.y()
else cubeInstance.y(false)
startX = e.clientX
}
// Vertical drag rotates around X axis
if (Math.abs(dy) > 30) {
if (dy > 0) cubeInstance.x()
else cubeInstance.x(false)
startY = e.clientY
}
}
This code tracks pointer movement and triggers rotations when the movement exceeds a threshold (30 pixels), creating a natural and intuitive interaction.
Debugging and Optimization
Fixing the Event Listener Bug
Our third prompt addressed a critical bug in the event listener cleanup:
Uncaught TypeError: Cannot read properties of null (reading 'removeEventListener')
This error occurred because the cleanup function in our useEffect
hook was trying to remove event listeners from a canvas element that might no longer exist. The solution was to capture the canvas element reference inside the effect and check for its existence during cleanup:
useEffect(() => {
const canvasEl = canvasRef.current
if (!canvasEl) return undefined
// Setup code...
return () => {
if (canvasEl) canvasEl.removeEventListener('pointerdown', onPointerDown)
window.removeEventListener('pointermove', onPointerMove)
window.removeEventListener('pointerup', onPointerUp)
}
}, [])
This pattern is a common solution for React's strict mode, which mounts, unmounts, and remounts components during development to help identify side effects.
Conclusion
This Rubik's Cube project demonstrates how quickly we can create interactive 3D experiences in the browser by leveraging specialized libraries. With just three prompts, we built a fully functional Rubik's Cube that users can manipulate through both button controls and direct pointer interaction.
The project showcases several important web development concepts:
- Component-based architecture with React
- 3D rendering with Three.js
- Event handling for pointer/touch interactions
- Proper cleanup of event listeners to prevent memory leaks
- Library integration to avoid reinventing complex functionality
While this implementation focuses on core functionality, it could be extended with features like:
- Cube state persistence
- Scramble and solve algorithms
- Custom color schemes
- Animation speed controls
- Mobile-optimized controls
Try the live demo and explore the possibilities of 3D web applications!