Cannot interact with JavaScript API of model-viewer component after updating a state in a React function component

143 views Asked by At

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-viewerelement 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?

0

There are 0 answers