I'm using the new _app.js
file for Next.js to try and create a page transition such as a fade or slide but can't get my head around it. I'm using Styled Components to apply the styling. My structure looks like this currently:
_app.js
import App, { Container } from 'next/app';
import React from 'react';
import PageTransition from '../components/PageTransition';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<PageTransition>
<Component {...pageProps} />
</PageTransition>
</Container>
);
}
}
export default MyApp;
components/PageTransition/index.jsx
import { Transition } from 'react-transition-group';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Root from './index.style';
class PageTransition extends Component {
state = {
active: true,
}
componentDidUpdate() {
// Check if children are different to previous children
// If true, set this.state.active to false
// After 1000ms transition, set to true again
}
render() {
const { children } = this.props;
const { active } = this.state;
return (
<Transition in timeout={1000}>
<Root id="root" active={active}>
{children}
</Root>
</Transition>
);
}
}
export default PageTransition;
components/PageTransition/index.style.js
import styled from 'styled-components';
const Root = styled.div`
opacity: ${p => (p.active ? 1 : 0)};
transition: opacity 1000ms;
`;
export default Root;
Would this be a correct approach? If so, I don't think componentDidUpdate is soon enough to catch the page change. My attempts at this work but it does a render and you see a flash of the page before the state updates again to tell it to start the fade out then fade in. I would need to update the state before the render.
Is there a way to handle this with React Transition Group? I did look at next-page-transitions but wasn't comfortable that the package has low usage and I just wanted a simpler solution.
I was recently looking into how to accomplish this as well. Looking through the brief documentation for the next-page-transitions, achieving a simple fade-in/fade-out page transition is as simple as using this inside the App component render method:
render() {
const { Component, pageProps } = this.props
return (
<Container>
<PageTransition timeout={300} classNames="page-transition">
<Component {...pageProps} />
</PageTransition>
<style jsx global>{`
.page-transition-enter {
opacity: 0;
}
.page-transition-enter-active {
opacity: 1;
transition: opacity 300ms;
}
.page-transition-exit {
opacity: 1;
}
.page-transition-exit-active {
opacity: 0;
transition: opacity 300ms;
}
`}</style>
</Container>
)
}
However, I see you're attempting to use react-transition-group. That's the library you'll want to use if you hope to achieve more complex animations.
Here's an example from the App component render method of a quick test I did using react-transition-group:
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<div ref={x => (this.comp = x)}>
<TransitionGroup>
<Transition
in={this.props.in}
key={this.props.router.route}
timeout={{
enter: 2000,
exit: 2000
}}
mountOnEnter={false}
unmountOnExit={true}
onEnter={this.enterTransition}
>
<Component {...pageProps} />
</Transition>
</TransitionGroup>
</div>
</Container>
);
}
Note: I'm using GSAP to animate, and in this test I was doing, I needed to have a reference to the contents of the component on initial mount. If you don't need this then you can get rid of that div and then just make sure to provide onEnter with a function that will be called upon every page navigation.
enterTransition = node => {
TweenMax.fromTo(node, 0, { x: 0, opacity: 0 }, {x: 200, opacity: 1});
}
The node will be the next component to mount. And there's also an onExit hook which you can use to target the exiting node in a similar fashion.