Custom Hooks
Video Lecture
Section | Video Links | |
---|---|---|
Custom Hooks : Part 1 | ||
Custom Hooks : Part 2 |
Description
In this example, we will create a minimal hook that listens for keyboard presses.
Part 1
In the first part of this lesson, we will use the key presses to move the box. The Box will move at 1 unit per second.
./src/useKeyboard.jsx
import { useEffect, useRef } from 'react'
export default function useKeyboard() {
const keyMap = useRef({})
useEffect(() => {
const onDocumentKey = (e) => {
keyMap.current[e.code] = e.type === 'keydown'
}
document.addEventListener('keydown', onDocumentKey)
document.addEventListener('keyup', onDocumentKey)
return () => {
document.removeEventListener('keydown', onDocumentKey)
document.removeEventListener('keyup', onDocumentKey)
}
}, [])
return keyMap.current
}
./src/Box.jsx
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
import useKeyboard from './useKeyboard'
export default function Box(props) {
const ref = useRef()
const keyMap = useKeyboard()
useFrame((_, delta) => {
keyMap['KeyA'] && (ref.current.position.x -= 1 * delta)
keyMap['KeyD'] && (ref.current.position.x += 1 * delta)
keyMap['KeyW'] && (ref.current.position.z -= 1 * delta)
keyMap['KeyS'] && (ref.current.position.z += 1 * delta)
})
return (
<mesh ref={ref} {...props}>
<boxGeometry />
<meshBasicMaterial color={0x00ff00} wireframe />
</mesh>
)
}
./src/App.jsx
import { Canvas } from '@react-three/fiber'
import Box from './Box'
import { Stats, OrbitControls } from '@react-three/drei'
export default function App() {
return (
<Canvas camera={{ position: [1, 2, 3] }}>
<Box position={[0, 0.5, 0]} />
<OrbitControls />
<axesHelper args={[5]} />
<gridHelper />
<Stats />
</Canvas>
)
}
Part 2
In the second part of this lesson, we add more boxes to the scene, and be able to move 1 or more boxes at the same time.
./src/Box.jsx
import { useRef, useState } from 'react'
import { useFrame } from '@react-three/fiber'
export default function Box(props) {
const ref = useRef()
const keyMap = props.keyMap
const [selected, setSelected] = useState(props.selected)
useFrame((_, delta) => {
keyMap['KeyA'] && selected && (ref.current.position.x -= 1 * delta)
keyMap['KeyD'] && selected && (ref.current.position.x += 1 * delta)
keyMap['KeyW'] && selected && (ref.current.position.z -= 1 * delta)
keyMap['KeyS'] && selected && (ref.current.position.z += 1 * delta)
})
return (
<mesh ref={ref} {...props} onPointerDown={() => setSelected(!selected)}>
<boxGeometry />
<meshBasicMaterial color={0x00ff00} wireframe={!selected} />
</mesh>
)
}
./src/App.jsx
import { Canvas } from '@react-three/fiber'
import Box from './Box'
import { Stats, OrbitControls } from '@react-three/drei'
import useKeyboard from './useKeyboard'
export default function App() {
const keyMap = useKeyboard()
return (
<Canvas camera={{ position: [1, 2, 3] }}>
<Box position={[-1.5, 0.5, 0]} keyMap={keyMap} />
<Box position={[0, 0.5, 0]} keyMap={keyMap} selected />
<Box position={[1.5, 0.5, 0]} keyMap={keyMap} />
<OrbitControls />
<axesHelper args={[5]} />
<gridHelper />
<Stats />
</Canvas>
)
}
Rules of Hooks
When creating hooks versus components, remember to follow these naming conventions.
-
Hook names must start with
use
followed by a capital letter, likeuseState
(built-in) oruseKeyboard
. Theuse
prefix enables the linter plugin to automatically check for violations of rules of Hooks. -
React component names, versus hooks, must start with a capital letter, such as in
App
,Box
andPolygon
.
Clean Up
If using useEffect
within your hook, you can optionally specify how to "clean up" after them by returning a callback. For example, in this useKeyboard
example, when it is unloaded, the useEffect
completes, and removes the keyDown
and keyUp
events from the HTML DOM.
If I didn't provide the option to remove the keyDown
and keyUp
event listeners, then every time this component was refreshed, due to a state or props change, then the DOM would keep accumulating new event listeners on each re-render of the component that uses this hook.
Execute getEventListeners(document)
in the browser console, to see how many event listeners are currently attached to your HTML DOM.
Working Example
Useful Links
Custom Hooks | reactjs.org |
Rules of Hooks | reactjs.org |
HTML DOM | w3schools.com |
GitHub Branch
git clone https://github.com/Sean-Bradley/React-Three-Fiber-Boilerplate.git
cd React-Three-Fiber-Boilerplate
git checkout customHook
npm install
npm start