Anxiny
Posted on April 20, 2021
This method may not be the best way for NextJS v12+ since new API is provided.
Before we start building any component for the transition, let's briefly talk about how NextJS renders pages.
First, let's take a look at _app.js
:
export default function MyApp({ Component, pageProps }) {
return (
<Component {...pageProps} />
);
}
The "_app.js" is the entry point for NextJS to start render page. When you navigation to a different page, the page component pass to MyApp as Component
.
Therefore, in order to make a transition effect, we need to prevent
NextJS from rendering the new page before transition effect is done.
Now, let's create the layout component with some navigation links:
export default function TransitionLayout({ children }) {
return (
<div>
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
</nav>
<div>
{children}
</div>
</div>
);
}
And add to _app.js
function MyApp({ Component, pageProps }) {
return (
<TransitionLayout>
<Component {...pageProps} />
</TransitionLayout>
);
}
Now, let's start working on the TransitionLayout
First, we need to prevent the rendering the new page
We can add a state to hold the current children, and only render the displayChildren
.
We use children
as the default value for displayChildren
.
export default function TransitionLayout({ children }) {
const [displayChildren, setDisplayChildren] = useState(children);
return (
<div>
...
<div>
{displayChildren}
</div>
</div>
);
}
Now, if you click the link, the content of the page will not change.
Next, we add css and transition stage
.content {
opacity: 0;
background-color: cornflowerblue;
transition: 1s;
}
.fadeIn {
opacity: 1;
}
export default function TransitionLayout({ children }) {
const [displayChildren, setDisplayChildren] = useState(children);
const [transitionStage, setTransitionStage] = useState("fadeOut");
...
return (
<div>
...
<div
className={`${styles.content} ${styles[transitionStage]}`}
>
{displayChildren}
</div>
</div>
);
}
Now, the component will by default at 'fadeOut' stage, and we want to let it enter the 'fadeIn' stage at first time render, so let's add:
useEffect(() => {
setTransitionStage("fadeIn");
}, []);
Next, we want the component to enter 'fadeOut' when new children is received.
useEffect(() => {
if (children !== displayChildren) setTransitionStage("fadeOut");
}, [children, setDisplayChildren, displayChildren]);
And, render new children when 'fadeOut'is done, then re-enter 'fadeIn' stage.
...
return(
...
<div
onTransitionEnd={() => {
if (transitionStage === "fadeOut") {
console.log("fading out");
setDisplayChildren(children);
setTransitionStage("fadeIn");
}
}}
className={`${styles.content} ${styles[transitionStage]}`}
>
{displayChildren}
</div>
)
And, here is the demo and the completed code for the layout component:
Node: The demo will take sometime for CodeSandbox to start.
import Link from "next/link";
import { useState, memo, useEffect } from "react";
import styles from "./Layout.module.css";
export default function TransitionLayout({ children }) {
const [displayChildren, setDisplayChildren] = useState(children);
const [transitionStage, setTransitionStage] = useState("fadeOut");
useEffect(() => {
setTransitionStage("fadeIn");
}, []);
useEffect(() => {
if (children !== displayChildren) setTransitionStage("fadeOut");
}, [children, setDisplayChildren, displayChildren]);
return (
<div>
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
</nav>
<div
onTransitionEnd={() => {
if (transitionStage === "fadeOut") {
console.log("fading out");
setDisplayChildren(children);
setTransitionStage("fadeIn");
}
}}
className={`${styles.content} ${styles[transitionStage]}`}
>
{displayChildren}
</div>
</div>
);
}
Thank you all!!
Posted on April 20, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.