For some reason, it seems the texture update is queued but never rendered. Resizing the browser window or scrolling makes it render correctly. I can also see for a very short time the initial texture being applied right before the new texture when switching textures.
useLayoutEffect is a version of useEffect that fires before the browser repaints the screen. the result of TextureLoader is only returned after the texture has been loaded meaning you’ve got a race condition eg. the screen is being rendered before the texture has loaded, have you considered using useTexture from within drei?
You can also see the issue I encountered with normal maps (or any kind of maps). Compare this to the first codesandbox. The front part is much darker and you can see strong edges on the shoulders. This does not happen when using the TextureLoader. (I know the code to set the new texture is wrong in this case, but since the initial texture has the normal map I just need to change a prop to trigger the rerender to show the issue)
useTexture is nothing else than new THREE.TextureLoader().load(url, data => … it cannot have an effect on normals etc. you should always prefer useTexture though because it prevents jank, it will pre-emptively upload the texture to the gpu. ootb threejs will only start to upload once the renderer “sees” a texture, if it’s in frustum.
a normal texture can behave messed up, in threejs, if it has the from color space (it should be linear), and three expects textures to be upside down (flipY=false)
other than that the sandbox i just tried makes no sense to me, it has a useLayoutEffect with async texture loaders in it (that means the results will be ready … whenever). that completely drowns what useLoader does, it destroys suspense, so the results is practically a race condition and cannot be awaited any longer. if you have changed it to useTexture let’s have another look.