简体   繁体   中英

Is it possible to make a `useIsAnimating` hook in React?

So I have a feature in my React app where it would be incredibly useful to know if an element is animating/going through a keyframe.

I spent all day yesterday trying tons of iterations through something like the below and lots of other abstractions, but can't seem to get it to work. Any thoughts or is this just not possible?

function useIsAnimating(ref) {
  const [isAnimating, setIsAnimating] = React.useState(false);

  useEffect(() => {
    ref?.current.addEventListener('animationstart', setIsAnimating(true)); 
    ref?.current.addEventListener('animationend', setIsAnimating(false)); 
    
    return () => {      
    ref?.current.removeEventListener('animationstart', setIsAnimating(true)); 
    ref?.current.removeEventListener('animationend', setIsAnimating(false)); 
    };
  }, [ref, setIsAnimating]);

  return isAnimating;
}

Without going too much into the reasons why you'd want to do this, the code above has a simple problem.

The second argument expected by addEventListener is a function that is supposed to be called when a given event gets triggered. Your code doesn't do that but instead it does this (after expanding it into a more readable form):

const animationStartListener = setIsAnimating(true);
ref?.current.addEventListener('animationstart', animationStartListener);
const animationEndListener = setIsAnimating(false);
ref?.current.addEventListener('animationend', animationEndListener);

For your code to work correctly you'd have to wrap the call to setIsAnimating(true) in a function, and pass that function as an argument to the addEventListener :

const animationStartListener = () => setIsAnimating(true);
ref?.current.addEventListener('animationstart', animationStartListener);
const animationEndListener = () => setIsAnimating(false);
ref?.current.addEventListener('animationend', animationEndListener);

or, using shorter syntax:

ref?.current.addEventListener('animationstart', () => setIsAnimating(true));
ref?.current.addEventListener('animationend', () => setIsAnimating(false));

Another thing to remember is that when you're removing a listener, you have to pass the same reference to the function as was passed to the addEventListener . The correct way to do this in React using React Hooks is by the use of useCallback hook:

function useIsAnimating(ref) {
  const [isAnimating, setIsAnimating] = React.useState(false);
  const handleAnimationStart = useCallback(
    () => setIsAnimating(true),
    [setIsAnimating],
  );
  const handleAnimationEnd = useCallback(
    () => setIsAnimating(false),
    [setIsAnimating],
  );

  useEffect(() => {
    ref?.current.addEventListener('animationstart', handleAnimationStart); 
    ref?.current.addEventListener('animationend', handleAnimationEnd); 
    
    return () => {      
    ref?.current.removeEventListener('animationstart', handleAnimationStart); 
    ref?.current.removeEventListener('animationend', handleAnimationEnd); 
    };
  }, [ref, handleAnimationStart, handleAnimationEnd]);

  return isAnimating;
}

Hope that helps you with your problem (and I didn't make any spelling mistakes here).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM