Function in useEffect continues to fire after component unmounts [duplicate]

I’m learning React and having trouble with component lifecycle, or some kind of memory leak. In the code below I’ve using [this codepen][1] in React, and it works, until my Preloader unmounts, and I continually get error below.

Is this a place for a cleanup function? How do I stop animate() from firing after the Preloader has been removed from the dom?

Error

"Uncaught TypeError: text2.current is null
setMorph Preloader.jsx:47
doMorph Preloader.jsx:42
animate Preloader.jsx:83"

In Parent Component

  const { initialLoad, setInitialLoad } = useGlobalState();

    useEffect(() => {
        if (initialLoad) {
          setTimeout(() => {
          setInitialLoad(false);
        }, 4600);
        }
      }, [initialLoad]);
    
    return (
        <div className='browser-wrapper'>
            {initialLoad &&
            <Preloader initialLoad={initialLoad} />
            }
           ...
    )

Preloader Component

const Preloader = ({ initialLoad }) => {
    const text1 = useRef(null)
    const text2 = useRef(null)

    // Insert Text String and Morph tuning adjusments
    const morphTime = .68;
    const cooldownTime = 0.12;
    let textIndex = texts.length - 1;
    let time = new Date();
    let morph = 0;
    let cooldown = cooldownTime;
    text1.current = texts[textIndex % texts.length];
    text2.current = texts[(textIndex + 1) % texts.length];

    function doMorph() {
        morph -= cooldown;
        cooldown = 0;

        let fraction = morph / morphTime;

        if (fraction > 1) {
            cooldown = cooldownTime;
            fraction = 1;
        }
        setMorph(fraction);
    }

    // A lot of the magic happens here, this is what applies the blur filter to the text.
    function setMorph(fraction) {
        text2.current.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`;
        text2.current.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`;

        fraction = 1 - fraction;
        text1.current.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`;
        text1.current.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`;

        text1.current.textContent = texts[textIndex % texts.length];
        text2.current.textContent = texts[(textIndex + 1) % texts.length];
    }

    function doCooldown() {
        morph = 0;

        text2.current.style.filter = "";
        text2.current.style.opacity = "100%";

        text1.current.style.filter = "";
        text1.current.style.opacity = "0%";
    }

    // Animation loop, which is called every frame.
    function animate() {
        requestAnimationFrame(animate);
        let newTime = new Date();
        let shouldIncrementIndex = cooldown > 0;
        let dt = (newTime - time) / 1000;
        time = newTime;

        cooldown -= dt;

        if (cooldown <= 0) {
            if (shouldIncrementIndex) {
                textIndex++;
            }

            doMorph();
        } else {
            doCooldown();
        }
    }

    useEffect(() => {
        if (text2.current !== null || undefined) {
        animate();
        } else {
            console.log('current text.2 is ' + text2.current)
        }
    }, [text2.current])
    

    return ( --- JSX follows
    ```


  [1]: https://codepen.io/Valgo/pen/PowZaNY?ref=devawesome.io