Simple Route Transitions in Next.js
Tags: javascript, nextjs, react, hooks
Published: 2022-07-20 12:17:52

I'm going to share a straightforward hook-based approach to Next.js page transitions. This article is **not** focused on CSS but on how to write custom react hooks.
To perform the CSS magic, we're going to use https://mui.com/material-ui/transitions/.

The first step is to identify a way to hijack the page renderer in Next.js, which you do by creating a file called `_app.js` in the `pages` folder.

```javascript
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}
export default MyApp
```

> https://nextjs.org/docs/advanced-features/custom-app
> Next.js uses the App component to initialize pages. You can override it and control the page initialization.

Our only interest is that `_app.js` will run before each page render, thus allowing us to override the layout and enable transition effects.

With this information, we can go ahead and write our custom hook.
```js
import { useEffect, useState } from 'react';

export default function useSimpleRouteTransition({ delay, children }) {
  const [transitionEnd, setTransitionEnd] = useState(true);
  const [displayChildren, setDisplayChildren] = useState(children);

  useEffect(() => {
    setTransitionEnd(false);
    const t = setTimeout(() => {
      setDisplayChildren(children);
      setTransitionEnd(true);
    }, delay);

    return () => {
      clearTimeout(t);
    };
  }, [children]);

  return {
    displayChildren,
    transitionEnd,
  };
}

```

To initialize, it requires two parameters:
- `delay` time in milliseconds for each transition.
- `children` are the react elements we receive from `_app.js`.

Let's analyze the code.
```js
const [transitionEnd, setTransitionEnd] = useState(true);
const [displayChildren, setDisplayChildren] = useState(children);
```
We define an internal state with `true` as starting value and make a copy of `children`.

Diving into the `useEffect` code.
```js
useEffect(() => {
  setTransitionEnd(false);
  const t = setTimeout(() => {
    setDisplayChildren(children);
    setTransitionEnd(true);
  }, delay);

  return () => {
    clearTimeout(t);
  };
}, [children]);
```
Every time `children` changes, a `setTimeout` is queued, which updates the new children after our set `delay`. To represent this action, we also toggle our internal `transitionEnd` from `false` to `true`.
Finally, the timeout clears whenever the component unmounts.

Putting everything together into a `Layout` component, it should look like this:
```js
import Link from 'next/link';
import { Box, Container, Stack, Fade } from '@mui/material';
import useSimpleRouteTransition from '@/hooks/useSimpleRouteTransition';

export default function Layout({ children }) {
  const { transitionEnd, displayChildren } = useSimpleRouteTransition({
    delay: 1000,
    children,
  });

  return (
    <Container maxWidth="lg">
      <Box
        sx={{
          flexFlow: 'column nowrap',
        }}
      >
        <Box mt={10} mb={0}>
          <h1>Page transitions with Next.js</h1>
        </Box>
      </Box>
      <Stack direction={'row'} spacing={2}>
        <Link href="/">index</Link>
        <Link href="/blog">blog</Link>
        <Link href="/links">Links</Link>
      </Stack>
      <Box sx={{ bgcolor: 'green', p: 2 }}>
        <Fade in={transitionEnd} timeout={1000}>
          <div>{displayChildren}</div>
        </Fade>
      </Box>
      <Box sx={{ bgcolor: 'darkblue', p: 2 }}>Footer</Box>
    </Container>
  );
}
```

Let's examine the implementation.

```js
const { transitionEnd, displayChildren } = useSimpleRouteTransition({
    delay: 1000,
    children,
  });
```
We call our custom hook with `delay: 1000` and `children` we get from our parent component. From there, we receive `displayChildren` and `transitionEnd`.

```js
<Fade in={transitionEnd} timeout={1000}>
  <div>{displayChildren}</div>
</Fade>
```
In our view, `displayChildren` instead of `children` are always shown. We wrap this view in a [Fade](https://mui.com/material-ui/transitions/#fade) component, which we set using `transitionEnd` to achieve a controlled fade.

And that's it! Let me know if it works for you.

You can find all the source code on GitHub:
https://github.com/calinalexandru/next-js-router-transitions