I am using the model-viewer
component from @google/model-viewer
in my React function component.
This is my code:
export default function RobotDetails() {
const [url, setUrl] = useState("../models/forks.glb");
const defaultCameraOrbit = "45deg 55deg 5m";
var mv = document.querySelector("model-viewer");
function changeModel(modelName: string) {
setUrl(`../models/${modelName}.glb`);
setCameraOrbit();
}
function setCameraOrbit(orbit: string = defaultCameraOrbit) {
mv!.cameraOrbit = orbit;
}
return (
...
<div className="modelview-container">
<model-viewer src={new URL(url, import.meta.url).href} camera-orbit="45deg 55deg 4m" camera-controls touch-action="none" interaction-prompt="none" />
<div className="model-select-left">
<button onClick={() => changeModel("table")}>Arm</button>
</div>
<div className="model-select-right">
<button onClick={() => changeModel("forks")}>Forks</button>
<button onClick={() => setCameraOrbit()}>Reset</button>
</div>
</div>
...
);
}
However I am having an issue with the useState
hook. I believe it may stems from this line in the react docs:
The set function only updates the state variable for the next render. If you read the state variable after calling the set function, you will still get the old value that was on the screen before your call.
setCameraOrbit
is a user-defined function that accesses the JavaScript API of the model-viewer
element in order to reset its camera orbit to a previously defined value, or a new value if specified.
setCameraOrbit
works fine when run on its own (e.g. the "Reset" button defined in the HTML), but when called by changeModel
it only works if the url doesn't change. Meaning that it only works if the url
state isn't new, causing setUrl
to not trigger a reactive update cycle.
For example, if the state of url
is currently set to "../models/forks.glb"
, and I click the "Forks" button, then setUrl
notices that the current state and new state are the same, and so does not trigger a re-render. In this case, the setCameraOrbit
call in changeModel
works as intended. However, if I instead click the "Arm" button, the current state of url
("../models/forks.glb"
) does not match the new state ("../models/table.glb"
), which causes setUrl
to trigger a re-render. In this case, the setCameraOrbit
in changeModel
does not work as intended. While it still runs, the camera does not visually update.
I also get this warning in the devtools console:
Element model-viewer scheduled an update (generally because a property was set) after an update completed, causing a new update to be scheduled. This is inefficient and should be avoided unless the next update can only be scheduled as a side effect of the previous update. See https://lit.dev/msg/change-in-update for more information.
I have looked into trying to force react to re-render before calling setCameraOrbit
, however there doesn't really seem to be a good way to do this in a functional react component.
What are some possible ways to make the setCameraOrbit
function work right after setUrl
is called?
Alternatively, what are some ways to run setCameraOrbit
whenever the src
attribute of the model-viewer
component updates?