Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 18 breaks outro animation #4

Open
chimp1nski opened this issue Aug 1, 2022 · 4 comments
Open

React 18 breaks outro animation #4

chimp1nski opened this issue Aug 1, 2022 · 4 comments

Comments

@chimp1nski
Copy link

When using React 18 (18.2.0), at the end of the outro animation, the transitioned timeline element flashes briefly before routing to the next page and playing the intro animation.

React-18-tweenpages-bug.mov

Beware

This is not happening when using React 17 (17.0.1).

I've also deployed a production build as well as disabled react strict-mode in dev in order to see if it's the useEffect double firing that React 18 comes with. Although I might have overlooked something, I can rule this out for now.

Both React versions were tested with Next.js 12.2.3 and it seems that it's not a Next issue.


My guess is that there is a lack of cleanup functions in the useEffect hook and therefore weirdness happening but honestly I don't even know where to start debugging this.


I am not expecting you @johnpolacek to fix this, am just leaving this here for others that might experience the same weirdness.
Thank you so much for this awesome guide on how to implement such complex animation stuff!

If I'll find a fix for this (other than reverting to React 17) I'll update this issue and create a PR.

Cheers

@fmaillet24
Copy link

fmaillet24 commented Aug 1, 2022

Same for me

@thismarioperez
Copy link

@fmaillet24 @chimp1nski @johnpolacek
I've got a fix in place in my implementation. Turns out in my case, the comparison of children and displayChildren was always invalidated. To get around that, I memoized children, and everything seems to work fine for me now.

Here's my code for reference:

// lib
import {useContext, useMemo, useState } from "react";
import useIsomorphicLayoutEffect from "@/hooks/useIsomorphicLayoutEffect";
import { TransitionContext } from "@/context/TransitionContext";

export default function TransitionLayout({ children }) {
    const [displayChildren, setDisplayChildren] = useState(children);
    const memoizedChildren = useMemo(() => children, [children]);
    const { timeline, resetTimeline } = useContext(TransitionContext);

    useIsomorphicLayoutEffect(() => {
        if (memoizedChildren !== displayChildren) {
            // console.log("children not equal");
            if (timeline.duration() === 0) {
                // there are no outro animations, so immediately transition
                setDisplayChildren(children);
            } else {
                timeline.play().then(() => {
                    // console.log("page transition played");
                    // outro complete so reset to an empty paused timeline
                    resetTimeline();
                    setDisplayChildren(children);
                });
            }
        }
    }, [memoizedChildren]);

    return <div>{displayChildren}</div>;
}

@kadekjayak
Copy link

any updates on it??

the code from @thismarioperez doesn't works for me.
to get around that, I add opacity: 0 and setTimeout before replacing the children.

const [displayChildren, setDisplayChildren] = useState(children);
const memoizedChildren = useMemo(() => children, [children]);
const { timeline } = useContext(TransitionContext);
const el = useRef<HTMLDivElement>(null);

useIsomorphicLayoutEffect(() => {
    if (memoizedChildren !== displayChildren) {
      if (timeline.duration() === 0) {
        // there are no outro animations, so immediately transition
        setDisplayChildren(children);
      } else {
        timeline.play().then(() => {
          // outro complete so reset to an empty paused timeline
          timeline.seek(0).pause().clear();

          if (el.current) {
            el.current.style.opacity = `0`;
          }

          /**
           * Avoid flashy
           */
          setTimeout(() => {
            setDisplayChildren(children);
            if (el.current) {
              el.current.style.opacity = `1`;
            }
          }, 200);
        });
      }
    }
  }, [memoizedChildren]);

  return (
    <div ref={el} style={{ opacity: 1 }}>
      {displayChildren}
    </div>
  );

I'm not sure if it's a good practice, but in my case blank white looks better than flashes..

@gcolombi
Copy link

@chimp1nski @fmaillet24 @kadekjayak

I found a way that works for me using next 13.1.2 and react 18.2.0. I used the router.asPath as condition/dependency in useIsomorphicLayoutEffect instead of the children prop because I don't want the animations to trigger if the current page link is clicked. To avoid the flash, I used timeline.pause().clear().

import useTransitionContext from '@/context/transitionContext';
import { useState } from 'react';
import { useRouter } from 'next/router';
import useIsomorphicLayoutEffect from '@/hooks/useIsomorphicLayoutEffect';

export default function TransitionLayout({
    children
}) {
    const router = useRouter();
    const [currentPage, setCurrentPage] = useState({
        route: router.asPath,
        children
    })
    const { timeline } = useTransitionContext();

    useIsomorphicLayoutEffect(() => {
        if (currentPage.route !== router.asPath) {
            if (timeline.duration() === 0) {
                /* There are no outro animations, so immediately transition */
                setCurrentPage({
                    route: router.asPath,
                    children
                })
            } else {
                timeline.play().then(() => {
                    /* outro complete so reset to an empty paused timeline */
                    timeline.pause().clear();
                    setCurrentPage({
                        route: router.asPath,
                        children
                    })
                })
            }
        }
    }, [router.asPath]);

    return (
        <div className='u-overflow--hidden'>
            {currentPage.children}
        </div>
    );
} 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants