pmndrs / drei

🥉 useful helpers for react-three-fiber
https://docs.pmnd.rs/drei
MIT License
8.43k stars 708 forks source link

TransformControls does not work (flicker on hover, not draggable) when setting state in useFrame() #2226

Open AndrewRayCode opened 2 days ago

AndrewRayCode commented 2 days ago

Codesandbox to reproduce: https://codesandbox.io/p/sandbox/react-three-fiber-forked-8tqxgj?file=%2Fsrc%2Fcomponents%2FBox.jsx%3A1%2C1-23%2C1

Problem description:

TransformControls don't work when you call setState in useFrame, including both react and zustand setters.

Hovering over the transform controls results in flickering, and they aren't draggable.

Relevant code:

import React from "react";
import { useState } from "react";
import { useFrame } from "@react-three/fiber";

import { TransformControls } from "@react-three/drei";

const Box = () => {
  const [state, setState] = useState();
  useFrame(() => {
    setState(Date.now());
  });
  return (
    <TransformControls mode="translate" translationSnap={0.5}>
      <mesh position={[0, 1, 0]}>
        <boxGeometry attach="geometry" />
        <meshBasicMaterial attach="material" wireframe="true" color="hotpink" />
      </mesh>
    </TransformControls>
  );
};

Suggested solution:

This should(?) work out of the box? Removing the setState causes it to work, but that is not an acceptable solution.

I note this does work if you move the useFrame() to the Plane.js component in the scene above - aka if it's in a sibling component.

This does not work if the parent component of the one containing the <TransformControls /> contains the useFrame/setState.

This also does not work if you add a key={0} prop to the TransformControls, which I tried to see if it's from mounting/unmounting.

abernier commented 1 day ago

https://r3f.docs.pmnd.rs/api/hooks#useframe

Be careful about what you do inside useFrame! You should never setState in there!

AndrewRayCode commented 1 day ago

@abernier I see ... wrapping my head around how to update state outside of useFrame - although it's unclear to me if that's directly related to this issue. If I set state intermittently, not every frame, it looks like it would cause the transformcontrols to break the dragging behavior on that frame

krispya commented 1 day ago

The issue is that the TransformControls is not being memoized. You can see a fix here: https://codesandbox.io/p/sandbox/react-three-fiber-forked-8tqxgj

const Box = () => {
  const [state, setState] = useState();
  useFrame(() => {
    setState(Date.now());
  });
  return <Inner />;
};

export default Box;

const Inner = React.memo(() => {
  return (
    <TransformControls mode="translate" translationSnap={0.5}>
      <mesh position={[0, 1, 0]}>
        <boxGeometry attach="geometry" />
        <meshBasicMaterial attach="material" wireframe="true" color="hotpink" />
      </mesh>
    </TransformControls>
  );
});

We should do this in the component itself.