Access changed state variables from inside custom hook (ReactJS)

Viewed 131

I have a custom useAnimationFrame Hook:

import React, { useRef } from 'react';

const useAnimationFrame = (callback: (arg0: number) => void) => {
  // Use useRef for mutable variables that we want to persist
  // without triggering a re-render on their change
  const requestRef = useRef<number>();
  const previousTimeRef = useRef<number>();

  const animate = (time: number) => {
    if (previousTimeRef.current) {
      const deltaTime = time - previousTimeRef.current;
      callback(deltaTime);
    }
    previousTimeRef.current = time;
    requestRef.current = requestAnimationFrame(animate);
  };

  React.useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => {
      if (requestRef.current) {
        cancelAnimationFrame(requestRef.current);
      }
    };
  }, []); // Make sure the effect runs only once
};

export default useAnimationFrame;

And I would like to use it this way, to update what is happening with each frame:

const exerciseRunning = useRef(true);
const directionForward = useRef(true);

const [progress, setProgress] = useState(0);

const [coord1, setCoord1] = useState({x: 0, y: 0});
const [coord2, setCoord2] = useState({x: 0, y: 0});

const [percentage, setPercentage] = useState(1);

useAnimationFrame((deltaTime) => {
    // Works!
    if (!exerciseRunning.current) {
      return;
    }

    // setProgress works!
    // directon.Forward works!
    // percentage does not work!
    setProgress((prevState) =>
      directionForward.current
        ? prevState + percentage * 1
        : prevState - percentage * 2
    );
  });

The references exerciseRunning and directionForward both work properly and so does the setProgress.

I know, that the matching() and percentage parts are set at the first render (for the inside of useAnimationFrame) and do not change afterward. How do I need to change the construct, so that I can use the updated state variables?

2 Answers

If I understand you correctly, you are saying that matching() is not working, as it always returns false? If so, your issue is not the reference but the function itself. You are trying to compare two different objects with === which will always return false as objects will be compared by reference. So you need to make a deep comparison:

const matching = () => coord1.x === coord2.x && coord1.y === coord2.y;

Okay, so my solution was pretty easy. So for anybody interested. I just added the option to include properties to my custom hook. So this is the custom hook:

import React, { useRef } from 'react';

// ADDED DependencyList

const useAnimationFrame = (callback: (arg0: number) => void, deps?: DependencyList) => {
  // Use useRef for mutable variables that we want to persist
  // without triggering a re-render on their change
  const requestRef = useRef<number>();
  const previousTimeRef = useRef<number>();

  const animate = (time: number) => {
    if (previousTimeRef.current) {
      const deltaTime = time - previousTimeRef.current;
      callback(deltaTime);
    }
    previousTimeRef.current = time;
    requestRef.current = requestAnimationFrame(animate);
  };

  React.useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => {
      if (requestRef.current) {
        cancelAnimationFrame(requestRef.current);
      }
    };
  }, [deps]); // <--- HERE!!!
};

export default useAnimationFrame;

And the code inside the functional component:

const exerciseRunning = useRef(true);
const directionForward = useRef(true);

const [progress, setProgress] = useState(0);

const [coord1, setCoord1] = useState({x: 0, y: 0});
const [coord2, setCoord2] = useState({x: 0, y: 0});

const [percentage, setPercentage] = useState(1);

useAnimationFrame((deltaTime) => {
    // Works!
    if (!exerciseRunning.current) {
      return;
    }

    // setProgress works!
    // directon.Forward works!
    // percentage does not work!
    setProgress((prevState) =>
      directionForward.current
        ? prevState + percentage * 1
        : prevState - percentage * 2
    );
  },
  [percentage]  
);
Related