This is a question about React router and its design, specially when considering pages transitions.
React is designed for modularity, and it excels on that area. But I found one task that seems pretty hard to do while maintaining isolation and modularity between the components: pages transitions.
I did read many articles and questions related to this, both in Medium and StackOverFlow among other sites; but up to the date I didn't read any description of a good architectural approach to solve the issues I will address here, and an explanation about why React router is not considering this common cases.
So lets consider a site with three pages, PageA, PageB and PageC:
If we want to use the same animation for mounting/unmounting these three pages, lets say a FadeIn for mounting and a FadeOut for unmounting, then we can use the React Transition Group component to wrap our <Switch>
component, and it will be in charge of animating the pages using CSS transitions. Here the CSS transitions will be controlled by <CSSTransition>
component with the class passed to ClassNames
.
<div className="Main">
<TransitionGroup component={null}>
<CSSTransition
key={locationKey}
appear={true}
classNames="Main"
timeout={{
enter: 300,
exit: 300,
}}
>
<Switch location={this.props.location}>
<Route
path="/PageA"
render={(props) => {
return <PageA />;
}}
/>
<Route
path="/PageB"
render={(props) => {
return <PageA />;
}}
/>
<Route
path="/PageC"
render={(props) => {
return <PageA />;
}}
/>
</Switch>
</CSSTransition>
</TransitionGroup>
</div>
Everything all right, we are happy and our site has some nice animations, both for mounting and unmounting.
Now we want to make different transitions for each mounting/unmounting action for each page: PageA to mount with FadeIn and unmount with a FadeOut, PageB mount with a slide from left to right and unmount sliding back to the left, and PageC to mount from bottom to top and unmount sliding back to bottom.
We will face two problems: if we use <CSSTransition>
component, we will have to pass different classNames
prop to it depending on the page rendered. I didn't implemented anything with this strategy yet, because there is another issue that concerns me: if follow this path, we will store the logic for animating our page components inside <CSSTransition>
component, and this is outside the pages. This doesn't look very modular and isolated to me, and the more books I read about software design, modularity and isolation, the more confused I am with this approach.
In an ideal world the logic for page animations should be defined in the main component of each page. Something like:
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
classForAnimation: '',
};
}
componentDidMount() {
this.setState({
classForAnimation: ' FadeIn',
});
}
render() {
return <div className={'Home' + this.state.classForAnimation}>
I'm Home page
</div>;
}
}
export default Home;
And some CSS:
.Home {
opacity: 0;
transition: opacity 0.5s ease;
}
.FadeIn {
opacity: 1;
}
This is only for mounting, really easy. So, if this is a better solution, ¿why we don't use it?
The reason is because React-router don't know about our transitions, and it will unmount the page to animate out immediately, without even waiting the animation to start —and, of course, to finish—. This is what <CSSTransition>
component does, but concentrating the logic of pages transitions outside the page components.
So, considering the case of multiple pages with different animations on mount/unmount, here are my questions:
A) Is there any reason why the logic for the transitions between pages shouldn't be stored inside the main component of each page?
B) Is there any reason why React-router were designed to unmount components ignoring the case of waiting for different type of events in different pages?
C) Is there a common approach to solve this issue that I am not considering?
D) Writing a custom react router from scratch would be an option?
Nik.