> For the complete documentation index, see [llms.txt](https://nova-graphix.gitbook.io/threefy/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://nova-graphix.gitbook.io/threefy/hooks/3.-hooks.md).

# 3. Hooks

Using the following hooks in threefy, you can conveniently load, create, and modify 3D models.

## 3.1 useThree

It is used when referencing internal objects (including methods) within the threefy engine or creating new objects through the references. The referenced are as shown in the following example code. That is, threefy, scene, camera, renderer, timer, controls, animator, raycaster, canvas, list, get, and set.

```js
const {
    threefy,
    scene,
    camera,
    renderer,
    timer,
    controls,
    animator,
    raycaster,
    canvas, // renderer.domElement
    list,
    get,
    set
} = uesThree()
```

```js
const scene = useThree( threefy => threefy.scene )
const camera = useThree( threefy => threefy.camera )
```

> **Note (v2.1):** called inside a `<ThreeCanvas>`, `useThree()` refers to **that** canvas. Each canvas has its own scene, camera, controls and renderer — see [1.3 More than one canvas](/threefy/getting-started/1.3-creating-a-scene.md). Called outside every canvas it answers with the only canvas there is; with several, it cannot know which one you meant, so read it inside the render and pass the value on.

Here, list and get are functions provided to refer more deeply to the inside of the threefy engine, and set is a function that allows changing values ​​inside the threefy engine. Examples of use of each function are as follows.

> Example usage of the function **get()**

```js
// objects
get( 'scene' )
get( 'camera' )
get( 'renderer' )
get( 'timer' )
get( 'controls' )
get( 'animator' )
get( 'raycaster' )

// object properties
get( 'scene.background' )
get( 'scene.fog' )
get( 'scene.children' )
get( 'camera.aspect' )
get( 'renderer.outputColorSpace' )
get( 'renderer.info' )
get( 'renderer.domElement' )
get( 'controls.keys' )
get( 'animator.mixer' )
get( 'animator.mixer.timeScale' )
get( 'raycaster.ray' )
get( 'raycaster.params' )

// object methods
get( 'camera.getViewSize' )( 1, new THREE.Vector2() )
get( 'camera.getWorldDirection' )( new THREE.Vector3() )
get( 'renderer.getPixelRatio' )() // device pixel ratio (or dpr)
get( 'renderer.getSize' )( new THREE.Vector2() )
get( 'renderer.getViewport' )( new THREE.Vector4() ) // [ x, y, width, height ]
get( 'renderer.getClearColor' )( new THREE.Color() )
get( 'timer.getElapsed' )()   // elapsed time in seconds
get( 'timer.getDelta' )()     // delta time in seconds
get( 'controls.getDistance' )()
get( 'animator.mixer.getRoot' )()

// multiple gets
const [ sceneId, camProj, toneMapping ] = get( 'scene.id', 'camera.projectionMatrix', 'renderer.toneMapping' )
const [ width, height ] = get( 'width', 'height' );
```

> Example usage of the function **list()**

```js
list( get( 'scene' ) )
list( get( 'camera' ) )
list( get( 'pipeline' ) )
list( get( 'animator.mixer' ) )
```

> Example usage of the function **set()**

```js
set( 'renderer.outputColorSpace', THREE.SRGBColorSpace );
set( 'renderer.toneMapping', THREE.ACESFilmicToneMapping );
set({
    'renderer.outputColorSpace': THREE.SRGBColorSpace,
    'renderer.toneMapping': THREE.ACESFilmicToneMapping,
});
set( 'renderer', {
    outputColorSpace: THREE.SRGBColorSpace,
    toneMapping: THREE.ACESFilmicToneMapping,
});
set( renderer, {
    outputColorSpace: THREE.SRGBColorSpace,
    toneMapping: THREE.ACESFilmicToneMapping,
});
set( 'scene.background', 0xcdcdcd )
set( 'scene.background', 'skyblue' )
set( 'scene.background', 'images/diffuse.jpg' )
set( 'scene.background', new THREE.TextureLoader().load('images/diffuse.jpg', (tex) => tex.colorSpace = THREE.SRGBColorSpace ) )
set( renderer, {
    setPixelRatio: 0.5,                 // renderer.setPixelRatio( 0.5 )
    setViewport: [ 100, 100, 700, 700 ] // renderer.setViewport( 100, 100, 700, 700 )
});
set( mesh.material, {
    color: [ 1, 1, 0 ],
    map: 'images/diffuse.jpg'
});
set( mesh, {
    rotation: [ Math.PI/6, Math.PI/3, 0 ],
    scale: [ 20, 10, 10 ], // or 20
});
```

## 3.2 useSetup | useRefEffect

This is a Hook created by combining React's useRef() and useEffect(), and is mainly used for initial setup or update of the created model.

```js
const ref = useSetup( ( box, scene ) => {
    box.scale.set( 10, 10, 10 )
    box.material.color.set( 0xffff00 )
    box.material.envMap = scene.background
})
```

```html
<box ref={ref} />
```

**useRefEffect** is the 2.0 name for this hook and remains available as an alias. It is **deprecated and will be removed in a future release** — calling it prints a console warning once. Replace it with useSetup; nothing else changes.

```js
const ref = useRefEffect( ( box, scene ) => {...} ) // same as useSetup
```

## 3.3 useHandle | useRefCallback

useHandle returns a **handle** for a callback. Think of it as a lever you hand to something else: the lever itself never changes, but pulling it always runs the **latest** callback.

```js
const handle = useHandle( ( ...args ) => {...} )
```

This matters wherever a callback is registered once and kept — the animation loop, a three.js event listener, a DOM listener. Passing a plain inline function there leaves you with two bad choices: pass a new function on every render (registrations pile up, or you have to re-attach each time), or keep the first one forever (its props and state go stale). A handle avoids both.

```js
const [ speed, setSpeed ] = useState( 1 )

// attached once, yet always sees the current speed
const onWheel = useHandle( ( event ) => setSpeed( speed + event.deltaY * 0.001 ) )

useEffect( () => {
    window.addEventListener( 'wheel', onWheel )
    return () => window.removeEventListener( 'wheel', onWheel )
}, [] ) // no dependencies — the handle never changes
```

You rarely need to reach for it directly — useFrame, useKeyDown and useKeyUp already use it internally, so callbacks passed to those hooks are registered once and stay current on their own.

**useRefCallback** is the 2.0 name for this hook and remains available as an alias. It is **deprecated and will be removed in a future release** — calling it prints a console warning once. Its optional `deps` argument is no longer needed and is ignored — a handle is always stable and always current, so there is nothing to invalidate.

```js
const callbackFn = useRefCallback( () => {...} ) // same as useHandle
```

## 3.4 useFrame

Within the animation loop of three.js, the callback function of the following useFrame function is performed. In the callback function, t is a parameter that represents current time (in seconds), threefy indicates the threefy engine, and dt is a parameter that represents delta time (in seconds). It is mainly used when dynamically updating the model over time.

```js
useFrame( ( t, threefy, dt ) => {
    const model = ref.current
    model.rotation.y = t * 0.5
})
```

**Calling it conditionally is fine.** Inside a threefy component, `useFrame` registers no React hooks of its own, so a call that happens on one render and not the next is safe — and so is calling it twice, or after an early return:

```js
if( font ) useFrame( t => { ... } )      // fine
```

The callback always sees your latest props, is never registered twice by a re-render, and is removed when the component unmounts or stops asking for it. The same applies to `useKeyDown` and `useKeyUp` (§3.9).

> **Note (v2.1):** this holds for components rendered inside `<ThreeCanvas>`. Called from a component outside the canvas, these three behave as ordinary hooks and the usual rules apply.

## 3.5 useSearch | useSearchObject

The following code is an example of searching all materials with the material name 'my-material-name' for all objects belonging to the scene and returning the results through the const result.

```js
const result = useSearch( 'Material', 'my-material-name' )
```

The following code is an example of searching texture objects for all objects belonging to the scene and returning the results as the const result.

```js
const result = useSearch( 'Texture' )
```

The following code is an example of searching objects whose names are ‘object01.name’ and ‘object02.name’ respectively for all Object3D objects belonging to the scene.

```js
const result = useSearch( 'Object3D', ['object01.name', 'object02.name'] )
```

The following code is an example of searching objects whose object type is ‘Mesh’ and whose name is ‘my-mesh-name’ for all objects belonging to a specific model.

```js
const result = useSearchObject( model, 'Mesh', 'my-mesh-name' )
```

Each object is returned **once**, even when more than one path leads to it — a material shared by ten meshes appears a single time in the result.

## 3.6 useAnimate

As follows, useAnimate supports three functions.

```js
const { replay, pause, flush } = useAnimate();
```

The pause() function forces the animation loop of three.js to stop. The replay() function restarts the animation loop to move the stopped state again. And the flush() function renders only the current frame while maintaining the stopped state.

## 3.7 useLoader

The following codes show the examples of loading a 3D model file, 2D image file, and audio file using useLoader.

The file extensions available for 3D model files are:\
**3DS, 3MF, AMF, DAE, FBX, MD2, GLB, GLTF, KMZ, OBJ(MTL), PLY, STL, SVG, VTK, VOX, WRL**

The file extensions available for 2D image files are:\
**PNG, JPG, GIF, BMP, etc.**\
(These image file formats can also be loaded by loadTextures(url) provided by threefy. It shares one texture cache with `useLoader`, so the same URL is fetched once and reused; it drives the loading spinner, recognises **HDR** files as well, and rejects with an error when a file cannot be read.)

The file extensions available for audio files are:\
**MP3, OGG, WAV**\
(These audio file formats can also be loaded by loadAudios(url) provided by threefy.)

```js
const modelA = useLoader( 'models/A.glb' )
const [ modelA, modelB ] = useLoader( ['models/A.zip', 'models/B.obj'] )
const [ textureA, textureB ] = useLoader( ['images/A.png', 'images/B.jpg'] )
const [ audioA, audioB ] = useLoader( ['audios/A.mp3', 'audios/B.ogg'] )
```

### Loading models with `<Suspense>`

When useLoader is called during render and the model has not arrived yet, it **suspends** — React's standard mechanism for "not ready". Wrap the part of the scene that depends on the model in a `<Suspense>` boundary and give it something to show meanwhile:

```jsx
import { Suspense } from 'react'

<Suspense fallback={<Text string={'Loading...'} height={2} color={0xffffff}/>}>
    <Model url={'models/A.glb'}/>
    <Model url={'models/B.zip'}/>
</Suspense>
```

Three things follow from this:

* **Everything inside a boundary appears together.** The models above show up at the same moment, no matter which file finished first. Reveal order no longer depends on download speed.
* **The fallback disappears on its own.** It is unmounted when loading completes, and threefy removes its 3D objects from the scene. A `<Text>` placeholder cleans itself up.
* **Wrap the slow part separately to let the rest through.** A nested boundary reveals independently, so a large model does not hold back the small ones:

```jsx
<Suspense fallback={<Text string={'Loading...'} height={2} color={0xffffff}/>}>
    <Model url={'models/cow.zip'}/>
    <Model url={'models/house.zip'}/>

    <Suspense fallback={<box scale={5} color={'red'}/>}>
        <Model url={'models/troll.zip'}/>  {/* big — arrives last */}
    </Suspense>
</Suspense>
```

A boundary is **not required**: `<ThreeCanvas>` already provides one that draws nothing while loading, so a model loads correctly with no setup at all. Add your own only where you want a placeholder or finer-grained reveal.

Calling useLoader **outside render** — inside a `useSetup` or `useHandle` callback, for example — does not suspend, since there is no render to suspend. It returns a placeholder object that threefy swaps for the real model in the scene once the file arrives.

**When a file cannot be loaded** — a wrong path, a 404 — useLoader raises an error rather than waiting forever. React hands it to the nearest error boundary, or reports it in the console when there is none. The failure is remembered, so a re-render does not retry in a loop.

> **Note (v2.1):** earlier versions neither resolved nor rejected in this case. The fallback stayed on screen with no error anywhere, which made a mistyped path very hard to find.

### Trying again after a failure — `retryLoad`

Because the failure is remembered, the same URL is not requested again on its own. That is deliberate: without it, React would re-render after the rejected promise and start the load over, forever. **You** decide when to reopen it:

```js
import { retryLoad } from 'threefy'

retryLoad( 'models/A.glb' )          // one URL (an array works too)
retryLoad()                          // every failure
```

The natural place to call it is the "try again" button of an error boundary:

```jsx
<button onClick={() => { retryLoad( url ); resetErrorBoundary() }}>
  Try again
</button>
```

Nothing else is needed — the next render starts a fresh request. Models that already loaded are untouched.

### Loading files from the user's own machine

`useLoader` reads files your app ships or serves. To let the person using your app bring their own, threefy has two entry points. They read the same 3D formats listed above.

```js
import { openFiles, dragDropFiles } from 'threefy'

openFiles()       // puts a "Choose files" button on the canvas
dragDropFiles()   // accepts files dropped onto the canvas
```

Both take the same three optional arguments and both return a **function that removes what they added** — pass it straight to an effect's cleanup:

| Argument    | Meaning                                                                                |
| ----------- | -------------------------------------------------------------------------------------- |
| `container` | Where to attach. Defaults to the threefy canvas.                                       |
| `onObject`  | Receives each loaded object. Omit it and threefy adds the object to the scene for you. |
| `onError`   | Receives `( error, filename )`. A console warning is printed either way.               |

```jsx
const OpenFilesButton = () =>
{
  const { scene } = useThree()

  useEffect(() => dragDropFiles( undefined, object => {
    object.scale.setScalar( 0.1 )
    scene.add( object )
  }), [])

  return null
}
```

While a file is dragged over the canvas, threefy highlights it — no CSS of your own is required. The highlight is drawn from the `.threefy-dragover` class; the element also carries a plain `.dragover` class you can style, and `.threefy-dragover::after { display: none }` turns the built-in highlight off.

## 3.8 useExporter

The following codes show the examples of using useExporter to save a 3D mesh, to a specific file format. (Note that the operating system will open a windows browser to give you a choice as to where you want to save the file.)

The available file extensions when saving as a 3D model file are as follows:\
**OBJ, DAE, GLB, STL, PLY**

```js
useExporter( 'fileName.glb', mesh )
useExporter( 'fileName.stl', mesh )
```

**The extension you pass decides the format.** Renaming the file in the browser's save dialog changes its name only, not its contents — a GLB saved as `model.dae` is still a GLB, and a viewer will fail to parse it.

**OBJ and DAE arrive as a `.zip`.** Neither format can carry its own images: an `.obj` needs its `.mtl`, and a `.dae` references image files. Downloading them one by one trips the browser's "multiple automatic downloads" block, so threefy bundles them into a single archive and prints what it wrote:

```
[threefy] saved 'model.zip' — model.dae + 3 texture file(s). A .dae cannot carry its images, so they are bundled.
```

The other formats (GLB, GLTF, STL, PLY) are saved as the single file you asked for.

## 3.9 useKeyDown | useKeyUp

The threefy engine supports useKeyDown and useKeyUp for keyboard interface. The following code shows an example of changing the color of a box using useKeyDown. That is, when you press the r, g, and b keys, the color of the box changes to red, green, and blue, respectively.

```js
const color = box.material.color
useKeyDown(( event ) =>
{
    switch( event.key )
    {
        case 'r': color.set('red'); break
        case 'g': color.set('green'); break
        case 'b': color.set('blue'); break
        default: color.set('skyblue'); break
    }
})
```

Use `event.code` instead of `event.key` when you want a physical key position regardless of keyboard layout — `'KeyW'`, `'ArrowUp'`, `'Escape'` — which is the usual choice for movement keys.

Like `useFrame`, these two may be called conditionally, more than once, or after an early return (§3.4).
