You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionModelA(){// Just a regular useLoader call without extensionsconstgltf=useLoader(GLTFLoader,URL_A);
...
}functionModelB(){constgltf=useLoader(GLTFLoader,URL_B,(loader)=>{// Load materials as red basic materialloader.register((parser)=>({asyncloadMaterial(){returnnewTHREE.MeshBasicMaterial({color: "red"});},}));});
...
}
You might expect that <ModelA> loads normally, while <ModelB> loads with a plugin that turns materials red. However, both models actually appear red. Imagine that <ModelA> and <ModelB> live in separate files, or even separate libraries—it becomes quite difficult to debug why this occurs.
Do you know what <MyComponent> code will display without seeing the .preload() call? The component logic suggests that it loads a resource from PATH_B, but the .preload() call causes it to load the cached resource from PATH_A. The fact that the API for these two invocations even allow different extensions is confusing.
IMO the global side effects of useLoader extensions are an anti-pattern that breaks from the immutable, unidirectional flow that I expect in React. The userland code doesn't communicates that one component is mutating some global object that affects other components, which makes it hard to understand runtime behavior from reading the code.
Readability aside, this API doesn't allow you to use loaders like you could in vanilla Three, where you might have different loader configurations for different assets of the same type.
Potential Solution
I think it'd make more sense to pass loader instances to useLoader rather than a constructor. This makes the shared nature of loaders more obvious in user code, and allows you to use different loader configurations for different resources.
It also makes the .preload API easier to understand—the current API allows you to pass different configurations across .preload and useLoader invocations even though these are intended to share a loader instance.
constgltfLoaderA=newGLTFLoader();// maybe need to store these in state?constgltfLoaderB=newGLTFLoader();gltfLoaderB.register((parser)=>({asyncloadMaterial(){returnnewTHREE.MeshBasicMaterial({color: "red"});},}));useLoader.preload(gltfLoaderA,URL_A);functionModelA(){constgltf=useLoader(gltfLoaderA,URL_A);
...
}functionModelB(){constgltf=useLoader(gltfLoaderB,URL_B);
...
}
Notice how here, even if I accidentally pass a different loader instance to .preload and the useLoader hook, it simply causes a cache miss instead of loading from the wrong path in my component.
In the current code, the suspend function sees these as different cache keys ([loader, URL_A] instead of [loader, URL_A, URL_B]) and would re-run the Loader.load function instead of returning the preloaded object.
This sandbox shows one way to address this, memoizing load results for each URL per-loader instead of just memoizing loader instances.
Problem
Consider this sandbox: https://codesandbox.io/p/sandbox/use-loader-extend-loader-issue-4w5dtq?file=%2Fsrc%2FApp.tsx%3A11%2C32
You might expect that
<ModelA>
loads normally, while<ModelB>
loads with a plugin that turns materials red. However, both models actually appear red. Imagine that<ModelA>
and<ModelB>
live in separate files, or even separate libraries—it becomes quite difficult to debug why this occurs.Or consider this code:
Do you know what
<MyComponent>
code will display without seeing the.preload()
call? The component logic suggests that it loads a resource fromPATH_B
, but the.preload()
call causes it to load the cached resource fromPATH_A
. The fact that the API for these two invocations even allow differentextensions
is confusing.IMO the global side effects of
useLoader
extensions are an anti-pattern that breaks from the immutable, unidirectional flow that I expect in React. The userland code doesn't communicates that one component is mutating some global object that affects other components, which makes it hard to understand runtime behavior from reading the code.Readability aside, this API doesn't allow you to use loaders like you could in vanilla Three, where you might have different loader configurations for different assets of the same type.
Potential Solution
I think it'd make more sense to pass loader instances to
useLoader
rather than a constructor. This makes the shared nature of loaders more obvious in user code, and allows you to use different loader configurations for different resources.It also makes the
.preload
API easier to understand—the current API allows you to pass different configurations across.preload
anduseLoader
invocations even though these are intended to share a loader instance.This sandbox illustrates a possible implementation: https://codesandbox.io/p/sandbox/use-loader-extend-loader-issue-forked-c7kw3z?file=%2Fsrc%2FApp.tsx%3A8%2C1-16%2C5
Notice how here, even if I accidentally pass a different loader instance to
.preload
and theuseLoader
hook, it simply causes a cache miss instead of loading from the wrong path in my component.Maybe there's some way to make this compatible with the existing API too.
The text was updated successfully, but these errors were encountered: