Trying to get models to load nicely with a spinner or loader

Hello, I have a collection of .glTF models I am loading and an HDR background.

My desire is that there be some kind of spinner or user notification indicating that these models are loading.

I’ve tried using the Loader component from Drei, but it has no effect seemingly. I’ve tried passing different components to the fallback prop of Suspense – but that causes an error that the passed component is not part of the THREE namespace.


return (
  <>
      <Canvas>    
             <Suspense fallback={null}>
             <ProductLine models={models} position={{x: position.x, y: 0, z: 0}}/> 
             <Environment files={hdr} background />      
             </Suspense>
      </Canvas>
       //</Loader/>
  </>
);

(I’ve removed some other components that are sitting outside the suspense like OrbitControls, Effects, and Lights)

I am passing an array of JSX components (that load .glTF models) as props to the ProductLine component, which loads them in one group. I thought that could be causing an issue, so I tried it with just one component that loads a single model (instead of ProductLine) but I still got a crash trying to set the fallback of suspense.

Update: I noticed that I can pass a Three object as a fallback, which makes sense – but I’m still having an issue because, I get sort of the following logic here,

  1. HDR background loads

  2. Pause of nothing happening for 4-5 seconds

  3. Fallback shows very briefly before rendering the glTF model

I have an async useEffect() running

useEffect(() => {
   (async () => {

      //Initializes the models prop to be rendered

   })();

}, []);

Could this be complicating my issue?

you can also use threes own defaultloadingmanager, this is what vanilla apps would do, it’s completely up to you how or where you use it.

everything inside suspense loads the same time, you can add a fallback. the fallback runs in the threejs tree, not the dom. but you could useEffect communicate intent upwards to the dom if you wanted. you can make loading fine grained by using multiple suspense, if you want stuff to show immediately, or if you have things that aren’t async.

<Canvas>
  <mesh geometry={staticModel} />
  <Suspense fallback={null>
    <AsyncModel />
  </Suspense>
  <Suspense fallback={null>
    <Environment preset="city" />
  </Suspense>

would also make sense to preload everything. all loaders have preload functions so that the models begin to fetch and parse immediately instead of waiting for the components to be mounted.

Thank you very much for your response. I will give this a try. I’ve switch to use a preload function – where before I was not using it

  useGLTF.preload(model);

Can I ask – what is happening here? When is the model “preloaded”? When my application launches?

Would you be able to give me a hint about this line,

using useEffect to communicate intent upwards to the dom?

its a pre-cache. suspense is a cache, so it starts fetching and parsing: new GLTFLoader().load(url, data => …). if the component requests useGLTF with the same key (the url) it will refer to either the completed loader result, or it will await the ongoing fetch.

using useEffect to communicate intent upwards to the dom?

function App() {
  const [isLoading, load] = useState(true)
  return (
    <Canvas>
      <Suspense fallback={<Handle load={load} />} />
    ...
    {isLoading && <div>pls wait</div>}

function Handle({ load }) {
  useEffect(() => {
    load(true)
    return () => load(false)
  }, [])
}

it’s completely open ended, you can make it your own. and again, defaultloadingmanager also works. you could also use tunnel-rat to write into the dom from within the canvas.

suspense is a good topic to study and read up on as it solves many of the traditional problems around dealing with async. there are patterns and features that can do wonders, for instance:

Thank you for the great response, I’ll give it a try!