Skip to content

Instantly share code, notes, and snippets.

@carloitaben
Created October 29, 2024 10:39
Show Gist options
  • Select an option

  • Save carloitaben/2dde02e1f72671e7a3ba4709efe94725 to your computer and use it in GitHub Desktop.

Select an option

Save carloitaben/2dde02e1f72671e7a3ba4709efe94725 to your computer and use it in GitHub Desktop.
React 19 page transitions
// Usage with Next App Router
"use client"
import { animate } from "framer-motion"
import TransitionRouter from "@/components/TransitionRouter"
import "./globals.css"
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<TransitionRouter
leave={async () => {
await animate(
document.documentElement,
{ opacity: [1, 0] },
{ duration: 1 }
)
}}
enter={async () => {
await animate(
document.documentElement,
{ opacity: [0, 1] },
{ duration: 1 }
)
}}
>
<body>{children}</body>
</TransitionRouter>
</html>
)
}
"use client"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { useTransitionRouter } from "@/components/TransitionRouter"
export default function Page() {
const [stage, startRouteTransition] = useTransitionRouter()
const router = useRouter()
return (
<div>
<div>Home</div>
<Link
href="/about"
onClick={(event) => {
event.preventDefault()
startRouteTransition(() => {
router.push("/about")
})
}}
>
{stage ? "Navigating to about..." : "Navigate to about"}
</Link>
</div>
)
}
import type { ReactNode } from "react"
import { useEffect, useState, useTransition } from "react"
import { createSafeContext } from "@/lib/context"
type TransitionRouterStage = "entering" | "leaving" | undefined
type TransitionRouterStartFunction = (
callback: () => void | Promise<void>
) => void
type TransitionRouterContext = [
stage: TransitionRouterStage,
startRouteTransition: TransitionRouterStartFunction
]
const [TransitionRouterContextProvider, useTransitionRouterContext] =
createSafeContext<TransitionRouterContext>("TransitionRouterContext")
export function useTransitionRouter() {
return useTransitionRouterContext()
}
type TransitionRouterCallback = () => Promise<void | VoidFunction>
type Props = {
children: ReactNode
leave: TransitionRouterCallback
enter: TransitionRouterCallback
}
export default function TransitionRouter({ children, leave, enter }: Props) {
const [shouldEnter, setShouldEnter] = useState(false)
const [stage, setStage] = useState<TransitionRouterStage>()
const [_, startTransition] = useTransition()
useEffect(() => {
if (!shouldEnter) return
setStage("entering")
startTransition(async () => {
await enter().then((cleanup) => cleanup?.())
setStage(undefined)
setShouldEnter(false)
})
}, [enter, shouldEnter])
const startRouteTransition: TransitionRouterStartFunction = (callback) => {
setStage("leaving")
startTransition(async () => {
setShouldEnter(true)
await leave().then((cleanup) => cleanup?.())
await callback()
})
}
const value: TransitionRouterContext = [stage, startRouteTransition]
return (
<TransitionRouterContextProvider value={value}>
{children}
</TransitionRouterContextProvider>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment