Trying to use framer motion ("motion/react") with intercepted and parallel routes.
Answered
Austrian Black and Tan Hound posted this in #help-forum
Austrian Black and Tan HoundOP
We have a "aside" parallel route which is responsible for displaying any content on client side navigation within an aside.
The initial animations from framer work as intended when client side routing to the cart for the very first time. The motion components animate as intended. What's weird is if you use "router.back" and then go back to the cart route, no animation happens. Almost as if the aside route for "/cart" was never unmounted. I've tried manually unmounting it myself using:
But that doesn't seem to work either, does anyone know of a working solution for this in v16 of Nextjs or a potential solution? I feel like the initial animation from framer should always happen, even when going back and forth between the routes.
Any help here is greatly appreciated. Thank you so very much!
-app
---page.tsx
---layout.tsx
---@aside
-----default.tsx
-----(.cart)
-------page.tsx
---cart
-----page.tsxThe initial animations from framer work as intended when client side routing to the cart for the very first time. The motion components animate as intended. What's weird is if you use "router.back" and then go back to the cart route, no animation happens. Almost as if the aside route for "/cart" was never unmounted. I've tried manually unmounting it myself using:
const path = usePathname()
const inView === "/cart";
return inView && <motion.div>{/** other stuff here **/}</motion.div>But that doesn't seem to work either, does anyone know of a working solution for this in v16 of Nextjs or a potential solution? I feel like the initial animation from framer should always happen, even when going back and forth between the routes.
Any help here is greatly appreciated. Thank you so very much!
Answered by Austrian Black and Tan Hound
Ahhh, I found out a kind of janky solution, but it works flawlessly 🤓
"use client";
import { useRouter } from "next/navigation";
import { useState, useEffect, startTransition, useEffectEvent } from "react";
import { AnimatePresence, motion } from "motion/react";
import { Dialog } from "radix-ui";
export default function Sidecart() {
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();
useEffect(() => {
setIsOpen(true);
}, []);
const onAnimationComplete = useEffectEvent(() => {
if (!isOpen) router.back();
});
return (
<Dialog.Root
open={isOpen}
onOpenChange={(open) => startTransition(() => setIsOpen(open))}
>
<AnimatePresence>
{isOpen && (
<motion.span
variants={{
initial: {},
enter: {},
exit: {},
}}
initial="initial"
animate="enter"
exit="exit"
className="absolute h-[1px]"
onAnimationComplete={onAnimationComplete}
>
<Dialog.Portal forceMount>
<Dialog.Overlay asChild>
<motion.div
variants={{
initial: { opacity: 0 },
enter: { opacity: 1 },
exit: { opacity: 0 },
}}
className="fixed inset-0 bg-neutral-900/30"
/>
</Dialog.Overlay>
<Dialog.Content asChild forceMount>
<motion.div
variants={{
initial: { scale: 0.5, y: "10%", opacity: 0 },
enter: { scale: 1, y: 0, opacity: 1 },
exit: { scale: 0.5, y: 0, opacity: 0 },
}}
className="fixed left-1/2 top-1/2 max-h-[85vh] w-[90vw] max-w-[500px] -translate-x-1/2 -translate-y-1/2 rounded-md bg-default-surface p-[25px] shadow-[var(--shadow-6)] focus:outline-none"
>
<Dialog.Title className="m-0 text-[17px] font-medium text-mauve12">
Edit profile
</Dialog.Title>
<Dialog.Description className="mb-5 mt-2.5 text-[15px] leading-normal text-mauve11">
Make changes to your profile here. Click save when you're
done.
</Dialog.Description>
<div className="mt-[25px] flex justify-end">
<Dialog.Close asChild>
<button className="inline-flex h-[35px] items-center justify-center rounded bg-green4 px-[15px] font-medium leading-none text-green11 outline-none outline-offset-1 hover:bg-green5 focus-visible:outline-2 focus-visible:outline-green6 select-none">
Save changes
</button>
</Dialog.Close>
</div>
<Dialog.Close asChild>
<button
className="absolute right-2.5 top-2.5 inline-flex size-[25px] appearance-none items-center justify-center rounded-full text-violet11 bg-gray3 hover:bg-violet4 focus:shadow-[0_0_0_2px] focus:shadow-violet7 focus:outline-none"
aria-label="Close"
></button>
</Dialog.Close>
</motion.div>
</Dialog.Content>
</Dialog.Portal>
</motion.span>
)}
</AnimatePresence>
</Dialog.Root>
);
}9 Replies
Austrian Black and Tan HoundOP
Ahhh, I found out a kind of janky solution, but it works flawlessly 🤓
"use client";
import { useRouter } from "next/navigation";
import { useState, useEffect, startTransition, useEffectEvent } from "react";
import { AnimatePresence, motion } from "motion/react";
import { Dialog } from "radix-ui";
export default function Sidecart() {
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();
useEffect(() => {
setIsOpen(true);
}, []);
const onAnimationComplete = useEffectEvent(() => {
if (!isOpen) router.back();
});
return (
<Dialog.Root
open={isOpen}
onOpenChange={(open) => startTransition(() => setIsOpen(open))}
>
<AnimatePresence>
{isOpen && (
<motion.span
variants={{
initial: {},
enter: {},
exit: {},
}}
initial="initial"
animate="enter"
exit="exit"
className="absolute h-[1px]"
onAnimationComplete={onAnimationComplete}
>
<Dialog.Portal forceMount>
<Dialog.Overlay asChild>
<motion.div
variants={{
initial: { opacity: 0 },
enter: { opacity: 1 },
exit: { opacity: 0 },
}}
className="fixed inset-0 bg-neutral-900/30"
/>
</Dialog.Overlay>
<Dialog.Content asChild forceMount>
<motion.div
variants={{
initial: { scale: 0.5, y: "10%", opacity: 0 },
enter: { scale: 1, y: 0, opacity: 1 },
exit: { scale: 0.5, y: 0, opacity: 0 },
}}
className="fixed left-1/2 top-1/2 max-h-[85vh] w-[90vw] max-w-[500px] -translate-x-1/2 -translate-y-1/2 rounded-md bg-default-surface p-[25px] shadow-[var(--shadow-6)] focus:outline-none"
>
<Dialog.Title className="m-0 text-[17px] font-medium text-mauve12">
Edit profile
</Dialog.Title>
<Dialog.Description className="mb-5 mt-2.5 text-[15px] leading-normal text-mauve11">
Make changes to your profile here. Click save when you're
done.
</Dialog.Description>
<div className="mt-[25px] flex justify-end">
<Dialog.Close asChild>
<button className="inline-flex h-[35px] items-center justify-center rounded bg-green4 px-[15px] font-medium leading-none text-green11 outline-none outline-offset-1 hover:bg-green5 focus-visible:outline-2 focus-visible:outline-green6 select-none">
Save changes
</button>
</Dialog.Close>
</div>
<Dialog.Close asChild>
<button
className="absolute right-2.5 top-2.5 inline-flex size-[25px] appearance-none items-center justify-center rounded-full text-violet11 bg-gray3 hover:bg-violet4 focus:shadow-[0_0_0_2px] focus:shadow-violet7 focus:outline-none"
aria-label="Close"
></button>
</Dialog.Close>
</motion.div>
</Dialog.Content>
</Dialog.Portal>
</motion.span>
)}
</AnimatePresence>
</Dialog.Root>
);
}Answer
Austrian Black and Tan HoundOP
Yeah @Siberian , I think this is because they're using the new
It's definitely weird and my found solution is definitely a hack. The
Activity api here which means the component is pseudo mounted, it's just display: none. The above solution also doesn't work if someone just presses the back button to go to the previous route.It's definitely weird and my found solution is definitely a hack. The
ViewTransition api definitely works better, but it doesn't allow for nearly enough animation customization 🙁 .Siberian
Yup. That's what I thought too. I'm surprised barely anybody is mentioning this. I liked how it worked on the original PPR (v15) but seems like for now I have to skip out on
cacheComponents. I don't know of any workarounds so it is an unfortunate trade off.Looks like someone's mentioned it already on Framer Motion's repository
Austrian Black and Tan HoundOP
The way I found which works on both manual closing and through browser back button is completely unmount the component via:
export default function Component() {
const path = usePathname();
const isPath = path === "/you/path/here"
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();
useEffect(() => {
setIsOpen(true);
}, []);
return isPath && (
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
{/* Dialog stuff here */}
</Dialog.Root>
)
}@Siberian Looks like someone's mentioned it already on Framer Motion's repository
Austrian Black and Tan HoundOP
Yeah I saw that xD. Looks like they have the api already built out for[ early access](https://motion.dev/docs/react-animate-activity).
Siberian
Oh wow I didn't see that in EA 👀