@vctrl/hooks
v0.23.0
Published
vctrl/hooks is a React hooks package designed to simplify 3D model loading and management within React applications. It's part of the vectreal-core ecosystem and is primarily used in the vctrl/viewer React component and the official website application.
Readme
@vctrl/hooks
Browser-side React hooks for loading, optimizing, and exporting 3D models. The runtime counterpart to @vctrl/core, built for React apps that need to handle 3D files directly in the browser.
Installation
npm install @vctrl/hooks
# or
pnpm add @vctrl/hooksHooks overview
| Hook | Import path | Description |
| ------------------ | --------------------------------- | ------------------------------------------------------------------------------- |
| useLoadModel | @vctrl/hooks/use-load-model | Load and parse GLTF, GLB, and USDZ files from file lists or dropped directories |
| useOptimizeModel | @vctrl/hooks/use-optimize-model | Run glTF-Transform optimizations on loaded models |
| useExportModel | @vctrl/hooks/use-export-model | Export the current scene to GLB or glTF |
useLoadModel
Loads 3D files and exposes the parsed Three.js Object3D scene.
import { ModelProvider, useLoadModel } from '@vctrl/hooks/use-load-model'
function Uploader() {
const { load, file, isFileLoading } = useLoadModel()
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
void load(Array.from(e.dataTransfer.files))
}
if (isFileLoading) return <p>Loading...</p>
if (file?.model) return <p>Model loaded: {file.name}</p>
return (
<div onDrop={handleDrop} onDragOver={(e) => e.preventDefault()}>
Drop a file
</div>
)
}
export default function App() {
return (
<ModelProvider>
<Uploader />
</ModelProvider>
)
}Context and direct usage
useLoadModel can be used in two ways:
- Context mode: wrap your app with
ModelProvider, then consume withuseModelContext()anywhere in that tree. - Direct mode: call
useLoadModel()outside a provider to manage a local model state.
Return values
| Value | Type | Description |
| -------------------------- | ------------------------------------ | ----------------------------------------------------------- |
| file | ModelFile \| null | Loaded file metadata and Three.js model |
| isFileLoading | boolean | True while loading and parsing |
| progress | number | Progress value from 0 to 100 |
| load(filesOrDirectories) | Promise<void> | Load files or dropped FileSystemDirectoryHandle entries |
| loadFromData(options) | Promise<SceneLoadResult> | Load already-resolved server scene payload |
| loadFromServer(options) | Promise<SceneLoadResult> | Fetch and load scene from an API endpoint |
| reset | () => void | Clear the current model |
| on / off | Event helpers | Subscribe and unsubscribe to loader lifecycle events |
| optimizer | OptimizerIntegrationReturn \| null | Populated when the hook is called with useOptimizeModel() |
useLoadModel events
on and off support these typed events:
| Event | Payload |
| ---------------------- | ------------------- |
| multiple-models | File[] |
| not-loaded-files | File[] |
| load-start | null |
| load-progress | number |
| load-complete | ModelFile \| null |
| load-reset | null |
| load-error | Error \| unknown |
| server-load-start | string |
| server-load-complete | SceneLoadResult |
| server-load-error | Error \| unknown |
Scene loading option types
loadFromServer(options) uses:
| Field | Type | Description |
| --------------- | --------------- | --------------------------------------------- |
| sceneId | string | Scene identifier to fetch |
| serverOptions | ServerOptions | Endpoint, auth, and header configuration |
| applySettings | boolean | Whether scene settings are applied after load |
loadFromData(options) uses:
| Field | Type | Description |
| --------------- | --------------------- | -------------------------------------------------------------- |
| sceneId | string \| undefined | Optional scene identifier |
| sceneData | ServerSceneData | Already-resolved payload containing glTF, settings, and assets |
| applySettings | boolean | Whether scene settings are applied after load |
useOptimizeModel
Runs mesh simplification, deduplication, quantization, and normals optimization using glTF-Transform — geometry passes run in a Web Worker. Texture compression runs in the main thread via browser-native OffscreenCanvas encoding.
import { useOptimizeModel } from '@vctrl/hooks/use-optimize-model'
import { useLoadModel } from '@vctrl/hooks/use-load-model'
function Optimizer() {
const optimizer = useOptimizeModel()
const { file, optimizer: integrated } = useLoadModel(optimizer)
const handleOptimize = async () => {
if (!file?.model || !integrated) return
await integrated.applyOptimization(integrated.simplifyOptimization, {
ratio: 0.6,
error: 0.001
})
}
return (
<button onClick={handleOptimize} disabled={optimizer.loading}>
{optimizer.loading ? 'Optimizing...' : 'Optimize model'}
</button>
)
}Key API surface
| Method / State | Type | Description |
| ------------------------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| load(model) | (model: Object3D) => Promise<void> | Load a Three.js scene into the optimizer |
| loadFromServerSceneData(sceneData) | Promise<void> | Initialize optimizer from a server scene payload |
| applyOptimization(fn, opts?) | <T>(fn?: (opts?: T) => Promise<void>, opts?: T) => Promise<void> | Apply optimization and sync the optimized model back into loader state |
| simplifyOptimization(options?) | Promise<void> | Simplify mesh geometry |
| dedupOptimization(options?) | Promise<void> | Deduplicate model data |
| quantizeOptimization(options?) | Promise<void> | Quantize vertex attributes |
| normalsOptimization(options?) | Promise<void> | Recompute or normalize normals |
| texturesOptimization(options?) | Promise<void> | Run texture compression flow |
| getModel() | Promise<Uint8Array \| null> | Export the current optimized model as GLB binary |
| report / info | Objects | Optimization metrics and derived stats |
| loading / error | State | Optimization status |
Optimization option types
useOptimizeModel methods map directly to @vctrl/core/model-optimizer option types:
| Method | Option fields |
| ---------------------- | ------------------------------------------------------------------------------------------------------------- |
| simplifyOptimization | ratio?: number, error?: number |
| dedupOptimization | textures?: boolean, materials?: boolean, meshes?: boolean, accessors?: boolean |
| quantizeOptimization | quantizePosition?: number, quantizeNormal?: number, quantizeColor?: number, quantizeTexcoord?: number |
| normalsOptimization | overwrite?: boolean |
| texturesOptimization | resize?: [number, number], targetFormat?: 'webp' \| 'jpeg' \| 'png', quality?: number |
Texture compression runs browser-native via OffscreenCanvas. No server call is made.
useExportModel
Exports the current scene from ModelProvider context to a downloadable file.
import { useExportModel } from '@vctrl/hooks/use-export-model'
import type { ModelFile } from '@vctrl/hooks/use-load-model'
function ExportButton({ file }: { file: ModelFile | null }) {
const { handleThreeGltfExport } = useExportModel()
return (
<button onClick={() => void handleThreeGltfExport(file, true)}>
Download GLB
</button>
)
}Methods
| Method | Description |
| -------------------------------------------------------------- | ------------------------------------------------------------------- |
| handleThreeGltfExport(file, binary) | Export a loaded Three.js model to .glb or a zipped .gltf bundle |
| handleDocumentGltfExport(document, file, binary?, download?) | Export from a glTF-Transform Document |
binary = true writes .glb; binary = false writes a zipped .gltf package.
Additional exports
reconstructGltfFilesfrom@vctrl/hooksModelProvideranduseModelContextfrom@vctrl/hooks/use-load-model- Shared types such as
ModelFile,SceneLoadResult, andServerSceneData
Peer dependencies
| Package | Version |
| -------------------- | -------------- |
| react | ^18 \|\| ^19 |
| three | ^0.170 |
| @react-three/fiber | ^9 |
Related docs
Source
The full source and README live in packages/hooks.
License
AGPL-3.0-only. See LICENSE.md.
