Skip to main content

useLoader loads asynchronous resources — textures, models, and the like — and exposes them through solid-js' reactivity. Use it inside <Suspense> to handle loading states. By default it caches every resource in a shared LoaderCache.

Signature

function useLoader<TLoader extends Loader, TInput extends LoadInput<TLoader>>(
constructor: AccessorMaybe<Constructor<TLoader>>,
url: AccessorMaybe<TInput>,
options?: UseLoaderOptions<TLoader, TInput>,
): Resource<LoadOutput<TLoader, TInput>>

Pass a loader class and what to load; you get back a Solid Resource. Call it to read the loaded data (texture()), and it suspends while loading.

Parameters

ParameterTypeDescription
constructorConstructor<TLoader> (or an accessor)The loader class — TextureLoader, GLTFLoader, …
urlstring | string[] | Record<string, string> (or an accessor)What to load. A record or array loads several at once and preserves its keys. An accessor reloads when its value changes.
options (optional)UseLoaderOptions<TLoader, TInput>See Options.

Options

OptionTypeDescription
basestringBase URL for resolving relative paths.
cacheboolean | LoaderRegistryHow this call caches. See Caching.
onBeforeLoad(loader: TLoader) => voidRuns before loading starts — e.g. to set loader properties.
onLoad(resource) => voidRuns after the resource loads successfully.
Exact type
interface UseLoaderOptions<TLoader extends Loader<any, any>, TInput extends LoadInput<TLoader>> {
base?: string
cache?: boolean | LoaderRegistry
onBeforeLoad?(loader: TLoader): void
onLoad?(resource: LoadOutput<TLoader, TInput>): void
}

Caching

By default every resource is stored in a shared global cache, so loading the same URL twice returns the same resource. Control it per call with the cache option, or swap the global cache out entirely:

You wantDo this
The default global cacheomit cache, or pass cache: true
A custom cache for this callcache: myRegistry — a LoaderRegistry
No caching for this callcache: false
To replace the global cache everywhereset useLoader.cache = myRegistry, or undefined to disable caching globally

Examples

// A single resource — resolves to the loader's data type
const texture = useLoader(TextureLoader, "wood.jpg")
const gltf = useLoader(GLTFLoader, "model.gltf")
// A record — loads several at once, keys preserved
const textures = useLoader(TextureLoader, {
diffuse: "wood-diffuse.jpg",
normal: "wood-normal.jpg",
})
// A reactive URL — reloads when the signal changes
const [url, setUrl] = createSignal("texture.jpg")
const reloading = useLoader(TextureLoader, url)

A record reads back as a record, which slots straight into a material:

const textures = useLoader(TextureLoader, {
diffuse: "crate.gif",
normal: "brick-bump.jpg",
roughness: "roughness-map.jpg",
})
<T.MeshStandardMaterial
map={textures().diffuse}
normalMap={textures().normal}
roughnessMap={textures().roughness}
/>

An array of URLs works the same way — useful for a CubeTextureLoader, with loader properties passed as options:

const cubeTexture = useLoader(
CubeTextureLoader,
["px.jpg", "nx.jpg", "py.jpg", "ny.jpg", "pz.jpg", "nz.jpg"],
{
onBeforeLoad: loader => {
loader.mapping = CubeReflectionMapping
},
},
)
<T.Scene background={cubeTexture()} />
<T.MeshStandardMaterial envMap={cubeTexture()} />

Custom cache

A custom cache is any object conforming to the LoaderRegistry interface — two methods:

  • set — store a resource (or its promise) for a given loader and URL;
  • get — retrieve one for a given loader and URL, returning either the promise or the resolved value.

Pass it per call with cache, or assign it to useLoader.cache to replace the global cache.

Exact type
interface LoaderRegistry {
set<TLoader extends Loader<any, any>>(
loader: TLoader,
url: LoaderUrl<TLoader>,
data: PromiseMaybe<LoaderData<TLoader>>,
): void
get<TLoader extends Loader<any, any>>(
loader: TLoader,
url: LoaderUrl<TLoader>,
warn?: boolean,
): PromiseMaybe<LoaderData<TLoader>> | undefined
}

load — the lower-level primitive

useLoader is built on load, a plain async function that wraps loader.load in a promise — no caching, no Suspense, no reactivity. Reach for it when you need to await a resource imperatively: inside an event handler, a one-shot setup effect, or non-component code.

import { load } from "solid-three"
import { TextureLoader } from "three"
// Single URL → resolves to the loader's data type
const texture = await load(new TextureLoader(), "/wood.jpg")
// Record of URLs → resolves to a record of resources, keys preserved
const textures = await load(new TextureLoader(), {
diffuse: "/wood-diffuse.jpg",
normal: "/wood-normal.jpg",
})

It gives you the same URL-normalization and record-spread behavior as useLoader, without Solid's reactive resource machinery.

Exact type
function load<TLoader extends Loader<any, any>>(
loader: TLoader,
input: LoaderUrl<TLoader>,
): Promise<LoaderData<TLoader>>
function load<TLoader extends Loader<any, any>, TInput extends LoadInput<TLoader>>(
loader: TLoader,
input: TInput,
): Promise<LoadOutput<TLoader, TInput>>

See also

Last updated: 6/8/26, 11:20 AM

solid threeA SolidJS renderer for three.js — learn by reading.
Community
github