React 18 breaks outro animation
chimp1nski opened this issue · 4 comments
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
Same for me
@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>;
}
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..
@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>
);
}