'Reanimated 2 reusable animation in custom hook

How can I create a reusable React hook with animation style with Reanimated 2? I have an animation that is working on one element, but if I try to use the same animation on multiple elements on same screen only the first one registered is animating. It is too much animation code to duplicate it everywhere I need this animation, so how can I share this between multiple components on the same screen? And tips for making the animation simpler is also much appreciated.

import {useEffect} from 'react';
import {
  cancelAnimation,
  Easing,
  useAnimatedStyle,
  useSharedValue,
  withRepeat,
  withSequence,
  withTiming,
} from 'react-native-reanimated';

const usePulseAnimation = ({shouldAnimate}: {shouldAnimate: boolean}) => {
  const titleOpacity = useSharedValue(1);
  const isAnimating = useSharedValue(false);

  useEffect(() => {
    if (shouldAnimate && !isAnimating.value) {
      isAnimating.value = true;
      titleOpacity.value = withRepeat(
        withSequence(
          withTiming(0.2, {duration: 700, easing: Easing.inOut(Easing.ease)}),
          withTiming(
            1,
            {duration: 700, easing: Easing.inOut(Easing.ease)},
            () => {
              if (!shouldAnimate) {
                cancelAnimation(titleOpacity);
              }
            },
          ),
        ),
        -1,
        false,
        () => {
          if (titleOpacity.value < 1) {
            titleOpacity.value = withSequence(
              withTiming(0.2, {
                duration: 700,
                easing: Easing.inOut(Easing.ease),
              }),
              withTiming(
                1,
                {duration: 700, easing: Easing.inOut(Easing.ease)},
                () => {
                  isAnimating.value = false;
                },
              ),
            );
          } else {
            titleOpacity.value = withTiming(
              1,
              {
                duration: 700,
                easing: Easing.inOut(Easing.ease),
              },
              () => {
                isAnimating.value = false;
              },
            );
          }
        },
      );
    } else {
      isAnimating.value = false;
      cancelAnimation(titleOpacity);
    }
  }, [shouldAnimate, isAnimating, titleOpacity]);

  const pulseAnimationStyle = useAnimatedStyle(() => {
    return {
      opacity: titleOpacity.value,
    };
  });

  return {pulseAnimationStyle, isAnimating: isAnimating.value};
};

export default usePulseAnimation;

And I am using it like this inside a component:

const {pulseAnimationStyle} = usePulseAnimation({
  shouldAnimate: true,
});

return (
  <Animated.View
    style={[
      {backgroundColor: 'white', height: 100, width: 100},
      pulseAnimationStyle,
    ]}
  />
);


Solution 1:[1]

From their docs: CAUTION Animated styles cannot be shared between views.

To work around this you can generate multiple useAnimatedStyle in top-level loop (number of iterations must be static, see React's Rules of Hooks for more information).

https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 user3047039