React Native (Reanimated) – Texts alternation in a loop, with fade effect

I’m trying to implement an animation in React Native using the react-native-reanimated library, where two texts alternate visibility with a fade-in and fade-out effect. The desired behavior is that only one text is visible at a time, with the following sequence:

  1. Initial State:
text1 opacity = 1 (visible)
text2 opacity = 0 (invisible)
  1. After 3500 ms:
text1 fades out over 500 ms.
Immediately after text1 fades out, text2 fades in over 500 ms.
  1. After 3500 ms:
text2 fades out over 500 ms.
Immediately after text2 fades out, text1 fades in over 500 ms.
  1. Repeat the sequence indefinitely.

I tried something like this:

import { useCallback, useEffect } from "react";
import {
  useAnimatedStyle,
  useSharedValue,
  withDelay,
  withRepeat,
  withSequence,
  withTiming,
  Easing,
} from "react-native-reanimated";

/**
 * Handles the animations of the fading texts.
 *
 * @returns {import("@atoms/texts/types/texts.d").FadeTextsAnimationInterface}
 *   An interface for accessing the members of the hook.
 */
export default function useFadeTextsAnimation() {
  const text1Opacity = useSharedValue(1);
  const text2Opacity = useSharedValue(0);

  const text1AnimatedStyle = useAnimatedStyle(() => ({
    opacity: text1Opacity.value,
  }), []);

  const text2AnimatedStyle = useAnimatedStyle(() => ({
    opacity: text2Opacity.value,
  }), []);

  /**
   * Triggers the animation for both texts, creating a loop where the texts
   * fade in and out sequentially.
   *
   * @returns {void}
   */
  const animate = useCallback(() => {
    const timingConfig = { duration: 500, easing: Easing.linear };

    text1Opacity.value = withRepeat(withSequence(
      withDelay(6000, withTiming(0, timingConfig)),
      withDelay(7000, withTiming(1, timingConfig))
    ), -1);

    text2Opacity.value = withRepeat(withSequence(
      withDelay(6500, withTiming(1, timingConfig)),
      withDelay(6000, withTiming(0, timingConfig)),
      withDelay(500, withTiming(0, { duration: 0 }))
    ), -1);
  }, [text1Opacity, text2Opacity]);

  useEffect(() => {
    animate();
  }, [animate]);

  return { text1AnimatedStyle, text2AnimatedStyle };
};

This is working fine, but, sometimes, both texts overlap.