dagda1
dagda1

Reputation: 28830

triggering a requestanimation reactjs hook in a button click

I have this hook which is to abstract requestAnimationFrame:

export const useAnimationIncrease = ({
  easingName = EasingName.linear,
  diff: end,
  duration = 500
}: AnimationIncreaseProps) => {
  const [increase, setIncrease] = useState(0);
  const start = useRef<any>(null);

  useLayoutEffect(() => {
    let raf: number;

    const frame = (timestamp: number) => {
      if (!start.current) {
        start.current = timestamp;
      }

      const time = timestamp - start.current;

      // get percent of completion in range [0, 1]
      const percentRange = Math.min(time / duration, 1);

      const percent = easing[easingName as string](percentRange);

      setIncrease(increase + end * percent);

      if (time < duration) {
        raf = requestAnimationFrame(frame);
      }
    };

    raf = requestAnimationFrame(frame);

    return () => {
      cancelAnimationFrame(raf);
    };
  },              [end]);

  return increase;
};

it used like this:

const increase = useAnimationIncrease({ easingName: EasingName.inOutCubic, diff: 10 });

It will return the increase each time so I can use it for scrolling etc.

How would I trigger this in a button click though or am I approaching this wrong.

If I try this;

onClick={(e) => {
  const increase = useAnimationIncrease({ easingName: EasingName.inOutCubic, diff: 10 });
  // use increase
}

Then I get the error message:

Hooks can only be called inside the body of a function component.

Upvotes: 5

Views: 857

Answers (1)

Shubham Khatri
Shubham Khatri

Reputation: 281834

According to the rules of hooks

Only Call Hooks at the Top Level

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in depth below.)

And since you are violating that rule by calling hooks in onClick event handler, you are getting this warning

As a workaround you can set a variable which you can use to trigger the animation

export const useAnimationIncrease = ({
  easingName = EasingName.linear,
  diff: end,
  duration = 500
}: AnimationIncreaseProps) => {
  const [increase, setIncrease] = useState(0);
  const [startAnimation, setStartAnimation] = useState(0);
  const start = useRef<any>(null);

  useLayoutEffect(() => {
    let raf: number;

    const frame = (timestamp: number) => {
      if (!start.current) {
        start.current = timestamp;
      }

      const time = timestamp - start.current;

      // get percent of completion in range [0, 1]
      const percentRange = Math.min(time / duration, 1);

      const percent = easing[easingName as string](percentRange);

      setIncrease(increase + end * percent);

      if (time < duration) {
        raf = requestAnimationFrame(frame);
      }
    };

    raf = requestAnimationFrame(frame);

    return () => {
      cancelAnimationFrame(raf);
    };
  },[end, startAnimation]);

  return [increase, startAnimation, setStartAnimation];
};

and use it like

const [increase, startAnimation, setStartAnimation] = useAnimationIncrease({ easingName: EasingName.inOutCubic, diff: 10 });

onClick={(e) => {
   setStartAnimation(prev => prev + 1);
   // use increase now
}

Upvotes: 3

Related Questions