[Fiber] Issue with useMemo not being activated despite dependences not being changed

Hello,

I am currently working on a large project that will dynamically change. Thousands of boxes will be constantly loaded and unloaded, so memorizing them is important to avoid unnecessary re-calculations.

Here is a memorized example: Instance Mesh Test - CodeSandbox

You can see that it stores the information about the generates axis in a object in index.js, and then passes it so axis.js to create the blocks on the screen:

var testObj = {
  otherData: 0,
  data: {
    axisSize: [100, 100, 100],
    axisColor: ["red", "green", "blue"]
  }
};

I also memorized the blocks positions, so they will not change unless the testObj.data changes.

When you click on the blue boxes, the axis size should shrink by 1 on every side. However, this doesn’t happen. Logging the value of the axisSize shows it decreasing, but it never triggers the useMemo.

import "./styles.css";
import React, { useMemo } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, Instance, Instances } from "@react-three/drei";

export default function App(props) {
  var { axisSize, axisColor, axisVoxelSize } = props.testObj.data;
  console.log(axisSize);
  var memorizedCoords = useMemo(() => {
    console.log("calculating");
    const axisVoxelList = [];
    for (let i = 0; i < axisSize.length; i++) {
      let min = -(axisSize[i] * 2);
      let max = axisSize[i] * 2;
      for (let j = min; j <= max; j += 1) {
        if (i === 0) {
          axisVoxelList.push(
            <Instance
              position={[j, 0, 0]}
              color={axisColor[i]}
              key={`AXISLINEVOXEL${[j, 0, 0]}COORD${i}`}
            />
          );
        } else if (i === 1) {
          axisVoxelList.push(
            <Instance
              position={[0, j, 0]}
              color={axisColor[i]}
              key={`AXISLINEVOXEL${[0, j, 0]}COORD${i}`}
            />
          );
        } else if (i === 2) {
          axisVoxelList.push(
            <Instance
              position={[0, 0, j]}
              color={axisColor[i]}
              key={`AXISLINEVOXEL${[0, 0, j]}COORD${i}`}
              onClick={() => {
                console.log("clicked");
                props.testObj.data.axisSize = props.testObj.data.axisSize.map(
                  (x) => (x -= 1)
                );
                console.log("onClick says");
                console.log(props.testObj.data.axisSize);
              }}
            />
          );
        }
      }
    }
    return axisVoxelList;
    // Make sure the dependcey is every outside varaible used in the function
  }, [axisSize, axisColor]);
  return (
    <Canvas>
      <ambientLight />
      <OrbitControls position={[10, 10, 10]} />
      <Instances limit={memorizedCoords.length}>
        <boxGeometry args={axisVoxelSize} />
        <meshStandardMaterial />
        {memorizedCoords}
      </Instances>
    </Canvas>
  );
}

useMemo does a shallow compare on its dependencies, not a deep compare. This means that if you mutate the objects React won’t know that anything changed. Make sure that whatever is changing the dependencies is replacing them rather than mutating them. Or use something like useDeepMemo (not part of React, but there are various libs that offer this functionality.)

1 Like

like @viridia said, this is not how useMemo works. the depdencies or checked one by one at reference equality.

const result = useMemo(() => {
  console.log("i will run when either foo or bar have changed")
  return foo + bar
}, [foo, bar])

other than that:

  • it feels wrong, the issue is likely elsewhere and can be solved by composition. you could for instance make a memoized component (React.memo) that listens to state and if that state changes it writes out a view. i don’t see why useMemo should be anywhere near this, it’s highly unusual to stick components in there and looks odd.
  • keep in mind that drei/Instances have overhead over plain THREE instances. the upside is that you get to control objects separately but do you need this? i would prefer plain instances if i had to do thousands of objects.

well, and none of this is really threejs related. isn’t poimandres discord or react stackoverflow better suited? it’ll just distract here.

1 Like

So separate the logic that creates the instances outside of the calculation function?

  1. Create one function that generates the coordinates and memorize the results.
  2. Create a map function in the return instances which use the coordinates as position.

The calculations in this example are simple enough to not be a issue, but in the real program I am working on, the calculations can take 10 - 20 seconds. Creating the boxes on the screens doubles this time.

Thank you, I will try to find a react fourm.

My first refactoring of this would be to move everything from the useMemo into a child component. That should help with the mental model of what’s going on. Next I would probably consider what should be going into a store rather than being calculated in the view layer. If you have some calculation that is taking 10s then probably you should be storing it.