Page Transition Effect in NextJS
Tags: nextjs, react, css, animation
Published: 2021-04-20 01:08:04

> 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`:

```javascript
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:
```javascript

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`

```javascript
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`.

```javascript

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

```css
.content {
  opacity: 0;
  background-color: cornflowerblue;
  transition: 1s;
}

.fadeIn {
  opacity: 1;
}

```
```javascript
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:

```javascript
  useEffect(() => {
    setTransitionStage("fadeIn");
  }, []);

```

Next, we want the component to enter 'fadeOut' when new children is received.

```javascript
  useEffect(() => {
    if (children !== displayChildren) setTransitionStage("fadeOut");
  }, [children, setDisplayChildren, displayChildren]);

```

And, render new children when 'fadeOut'is done, then re-enter 'fadeIn' stage.

```javascript
  ...
  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.

{% codesandbox mm4ut %}
```javascript
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!!