Updating scene after fetch call returns from React component

Hi, I’m trying to update my scene after a fetch call.

I am using the SceneComponent provided by babylonjs-hook

My component has a simple button and it tries to update the scene:

import React, { useState, useEffect, useCallback } from "reactn";
import { ArcRotateCamera, Vector3, Color3, MeshBuilder } from "@babylonjs/core";
import { URL } from "../constants";
import wretch from "wretch";
import { Button } from "@material-ui/core";
import SceneComponent from "babylonjs-hook";

function extractLineData(data) {
  let lineMap = {};
  data.forEach((timeSlice, index) => {
    Object.keys(timeSlice).forEach((key) => {
      let coord = timeSlice[key];
      let vec = new Vector3(coord.X, coord.Y, coord.Z);
      vec.divideInPlace(new Vector3(1e11, 1e11, 1e11));
      if (key in lineMap) {
        lineMap[key].push(vec);
      } else {
        lineMap[key] = [vec];
      }
    });
  });
  return lineMap;
}

export default (props) => {
  const [sensorData, setSensorData] = useState([]);

  const fetchData = useCallback(() => {
    if (props.uuid == null) return;
    wretch(URL + "gravitySensor")
      .post({ UUID: props.uuid })
      .json((json) => {
        setSensorData(json.Data);
        console.log("FETCHED ");
      });
  }, [props.uuid]);

  useEffect(() => {
    fetchData();
  }, [props.uuid, fetchData]);

  const onSceneReady = (scene) => {
    var camera = new ArcRotateCamera(
      "Camera",
      0,
      0,
      10,
      new Vector3(0, 0, 0),
      scene
    );

    camera.setTarget(Vector3.Zero());
    const canvas = scene.getEngine().getRenderingCanvas();
    camera.attachControl(canvas, true);
  };

  const onRender = (scene) => {
    for (var i = 0; i < scene.meshes.length; i++) {
      scene.meshes[i].dispose();
      i--;
    }
    scene.clearColor = Color3.Black();

    let lineMap = extractLineData(sensorData);

    Object.keys(lineMap).forEach((key) => {
      MeshBuilder.CreateLines(key, { points: lineMap[key] }, scene);
    });
  };

  return (
    <div>
      <Button onClick={() => fetchData()}>REFRESH</Button>
      <SceneComponent
        antialias
        onSceneReady={onSceneReady}
        onRender={onRender}
        id="my-canvas"
        style={{ width: "100%", height: "100%" }}
      />
    </div>
  );
};

This will render the black background but it won’t render the sensorData object. If I hard code one of the responses into sensorData and disable the fetch the lines are rendered correctly. My guess is that onRender function is never updated and it stays the same. How can I ensure the onRender captures the latest value of sensorData ?

Pinging @brianzinn :slight_smile:

You don’t want to do all of that work in each onRender, which happens ~60 times/second. Just update your scene meshes themselves by creating those meshes after the fetch event (as you have done but outside that method). You are right also about the sensorData - I don’t think the closure will be maintained. I have come across something like this before.

Essentially, don’t do work in your onRender - update your scene elsewhere and it should work as expected. Let me know if I can help out. It’s the first time I have seen anybody using the hook NPM, so cool to see in practice :slight_smile:

Thanks for the reply @brianzinn ! I had an optimization to create lines only after the fetch returns but I removed it to simplify for the question. I solved my problem by keeping track of the scene thanks to your suggestion.

const [scene, setScene] = useState(null);
const onSceneReady = (scene) => {
    ...
    setScene(scene)
};

Then I did my updates right after the fetch. Thank you! :smile:

1 Like