Closed stnwk closed 8 years ago
There's an example right in the docs: https://react-router.now.sh/animated-transitions
No, @timdorr. The example does not at all answer my question? It uses React-Motion to transition background-color, but it doesn't show how to keep the old route still on screen, although the new one was already triggered.
Do you see what I mean? It's not a very good example I think, doesn't give much practical use.
OK, that's a fair assessment. Perhaps we can better show an example where we wrap a <Match>
with something that keeps it's children
rendered for a set period of time (sort of like with <ReactCSSTransitionGroup>
).
Yes, that's more of what I'm looking for. I think that'll be a good idea, as it's much more realistic use-case - rather than using react-motion.
I'm also trying to figure out how to get my page transitions going, it's nothing like v2/v3 😕
So, this is how I managed to use ReactTransitionGroup react-addons-transition-group
(see React docs) with React Router v4:
App.js
import React from 'react';
import Router from 'react-router/BrowserRouter';
import Match from 'react-router/Match';
import Link from 'react-router/Link';
// new
import AnimatedMatch from './AnimatedMatch';
const App = () => (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/topics">Topics</Link></li>
</ul>
<hr/>
<AnimatedMatch exactly pattern="/" component={Home} />
<AnimatedMatch pattern="/about" component={About} />
<AnimatedMatch pattern="/topics" component={Topics} />
</div>
</Router>
);
const Home = () => (
<div>
<h2>Home</h2>
</div>
);
const About = () => (
<div>
<h2>About</h2>
</div>
);
const Topics = () => (
<div>
<h2>Topics</h2>
</div>
)
This is basically the Basic Example from the current docs, i only just replaced the Match
Component with a custom AnimatedMatch
component.
AnimatedMatch.js
import React, { Component } from 'react';
import Match from 'react-router/Match';
import TransitionGroup from 'react-addons-transition-group'; // here comes the magic
import Page from './Page'; // new
export default class AnimatedMatch extends Component {
render() {
const data = this.props;
return (<Match
{...this.props}
children={({ matched, ...props }) => {
return (<TransitionGroup>
{matched && <Page>
<data.component {...props} />
</Page>}
</TransitionGroup>);
}}
/>);
}
}
Here I basically render the React-Router v4 Match
Component and make use of the children
prop to wrap our actual desired component data.component
(in that case) with a TransitionGroup
to set up the component with hooks to animate the underlying DOM.
For that matter we will need another component where we can define those hooks Page.js
.
Page.js
import React, { Component } from 'react';
import { findDOMNode } from 'react-dom';
export default class Page extends Component {
componentWillEnter(cb) {
// animate stuff, then call cb();
const element = findDOMNode(this);
cb();
}
componentWillLeave(cb) {
// animate stuff, then call cb();
const element = findDOMNode(this);
cb();
}
render() {
return this.props.children;
}
}
Here I only render the children
which is actually our desired component
from the Match
and set up hooks like componentWillEnter
and componentWillLeave
. For more infos on this API take a look at the official react docs
React Transition Group.
I don't really have the time to make a full PR, but I think this pretty much solves it and is a good example for some practical usage.
Hope it helps @HofmannZ :)
Hmm I implement the same thing for the Miss component but it does not seem to work as aspected: the AnimatedMiss file in my GitHub repo
While the implementation for Match works just fine: the AnimatedMatch file in my GitHub repo
Okay, @HofmannZ so you can't currently use lifecycle hooks to animate a <Miss>
component.
The <Miss>
component does currently not offer a children
prop which is needed in order to use lifecycles for animation.
I opened a new feature-request issue #4026 and explained the reason there too.
This could also be used to create a pinterest style modal #4029 . The docs could benefit from having a modal example as well.
Thanks @stnwk for your snippet! I was looking for a way to animate simple page transitions with react-router and transition groups and it did the trick. :ok_hand:
<MatchGroup>
will also make animation with transition group simpler
cf2555d9031c02780b1324d1880ddc4f5953ed71
example update coming soon, but basically you can do:
<CSSTransitionGroup {..whatever}>
<MatchGroup>
<Match pattern="/foo" component={Foo}/>
<Match pattern="/bar" component={Bar}/>
<Miss component={Nope}/>
</MatchGroup>
</CSSTransitionGroup>
Only one child will ever render in there, so it'll work like all other transition group usage.
@ryanflorence I don't think it will...
The Transition Group is only able to track changes and call events on its direct children.
You would need to do something like this
<MatchGroup WrapperComponent={TransitionGroup}>
<Match pattern="/foo" component={Foo}/>
<Miss component={Nope}/>
</MatchGroup>
Then in MatchGroup
render() {
const { children } = this.props
return (
<LocationSubscriber>
{(location) => {
const { matchedIndex, missIndex } = this.findMatch(location)
return (
<WrapperComponent>
{ matchedIndex != null ? (
children[matchedIndex]
) : missIndex ? (
children[missIndex]
) : null }
<WrapperComponent>
);
}}
</LocationSubscriber>
)
}
Something like that??
Point being Transition Groups need to be directly around the matched components. I would very much like to use Transition Groups with MatchGroups.
I struggled with understand how this would work for my switch routing. Eventually found a simple solution for what I was doing.
Which was basically wrapping my <Switch>
in a <Route>
that receives a location and key.
<BrowserRouter>
<div>
<Route component={TopMenu} />
<Route render={({ location }) => (
<CSSTransitionGroup transitionName="fade" transitionEnterTimeout={500} transitionLeaveTimeout={500}>
<Route location={location} key={location.key}>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/tickets" component={TicketsPage} />
<Route path="/event/:id" component={(params) => {
return <EventPage id={params.match.params.id} />
}}/>
<Route path="/search" component={(params) => {
return <SearchResultsPage queryString={params.location.search} />
}}/>
<Route path="*" component={FourOhFourPage} />
</Switch>
</Route>
</CSSTransitionGroup>
)}
/>
</div>
</BrowserRouter>
@ro-savage Thanks this works for me. This is currently the only working example I can find for v4.0 + CSSTransitionGroup
@ro-savage It hasn't worked for me yet.
@andersonsousa if you are following my example, and the routes are working but not the transitions, you might find that you haven't implemented CSSTransitionGroup
correctly. E.g. make sure you have created the correct classes, in my example fade
classes
.fade-enter {
opacity: 0.5;
z-index: 1;
}
.fade-enter.fade-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
If that doesn't solve the issue, you'll have to just keep playing. It works for me and I assume all the people who left emojis.
@ro-savage Actually I had forgot the order of components.
Route > CssTransition > Route > Switch > { Routes }
I had forgot this first Route which renders the CssTransition. Now its fully working. I only have one question. Is there any way to not animate some Routes?
What about if you use react-router-config?
I have tried: react-easy-transition throws propType errors and new page loads before old page can disappear react-router-transition Position absolute is pretty gross when components collapse react-css-transition-replace Looks promising but I can't get the structure right. (Used in example below)
I would like the cross fade with animated height Demo Here: react-css-transition-replace Demo
Related RR4 + react-css-transition-replace Thread: https://github.com/marnusw/react-css-transition-replace/issues/46
Here is my code:
App.js page:
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';
import routes from './routes';
const App = () => {
return (
<Router>
{renderRoutes(routes)}
</Router>
);
}
export default App;
Base.js page (Base class inside App.js):
import React from 'react';
import { renderRoutes } from 'react-router-config';
import ReactCSSTransitionReplace from 'react-css-transition-replace';
import { shape } from 'prop-types';
import Header from './Header';
import './Base.css';
import './styles/includes.css';
const Base = ({route, location}, {router}) => (
<div className="App">
<Header />
<ReactCSSTransitionReplace
transitionName="cross-fade"
transitionEnterTimeout={1000}
transitionLeaveTimeout={1000}
>
{renderRoutes(route.routes, { key: location.pathname,})}
</ReactCSSTransitionReplace>
</div>
);
Base.propTypes = {
location: shape({}),
};
export default Base;")
And by routes.js page:
import Base from './Base';
import Home from './Home';
import About from './About';
import NotFound from './NotFound';
import Change from './Change';
import OurTeam from './OurTeam';
import Tacos from './Tacos';
import Chicken from './Chicken';
import Veggie from './Veggie';
import Posts from './Posts';
import Post from './Post';
export default [
{ component: Base,
routes: [
{ path: '/',
exact: true,
component: Home,
},
{
path: '/about',
component: About,
name: 'About',
},
{
path: '/change',
component: Change,
name: 'Change',
},
{
path: '/our-team',
component: OurTeam,
name: 'OurTeam',
},
{
path: '/tacos',
component: Tacos,
routes: [
{ path: '/tacos/chicken',
component: Chicken,
},
{ path: '/tacos/veggie',
component: Veggie,
}
]
},
{
path: '/posts',
exact: true,
component: Posts,
name: 'Posts',
},
{
path: '/posts/:id',
component: Post
},
{
component: NotFound,
},
]
},
]
Css file:
.cross-fade-leave {
opacity: 1;
}
.cross-fade-leave.cross-fade-leave-active {
opacity: 0;
transition: opacity 1s ease-in;
}
.cross-fade-enter {
opacity: 0;
}
.cross-fade-enter.cross-fade-enter-active {
opacity: 1;
transition: opacity 1s ease-in;
}
.cross-fade-height {
transition: height .5s ease-in-out;
}
@uxlayouts your example not work. renderRoutes receive only one argument - its routes. How fix it?
@ro-savage above example works. But it hasn't been explained yet some other example where the component animation overlaps with the former component. It is to say, both components remain on screen for a short time, usually with the position: absolute
CSS property set and some opacity
and transform
transition involved.
In order to achieve such transition you'll have to handle the routes yourself. Don't have more than one <Route>
on the <Router>
. If there are more than one <Route>
the component associated for that route will be immediately unmounted without waiting for the transition group time out. So instead, we'll have just one <Route>
and we'll control when to show and hide components associated to different routes.
For example, using react-transition-group
2.x (please note the syntax and usage has changed from version 1.x), on a RandomComponent.jsx
:
// Other imports...
import { TransitionGroup, CSSTransition } from "react-transition-group";
function getPage(pathname) {
let component;
switch (pathname) {
case "/": component = <Component1/>; break;
case "/path2": component = <Component2/>; break;
case "/path3": component = <Component3/>;
}
return (
<CSSTransition
key={ pathname } // If you don't set a key the animation won't work
timeout={ 500 }
classNames="animation-classes">
{ component }
</CSSTransition>
);
}
export default function RandomComponent({ location }) {
return (
<TransitionGroup>
{ getPage(location.pathname) }
</TransitionGroup>
);
}
The CSS for the transitions:
.animation-classes {
&-enter {
position: absolute;
opacity: 0;
transform: translate3d(8%, 0, 0);
}
&-enter&-enter-active {
z-index: 1;
opacity: 1;
transform: translate3d(0, 0, 0);
transition: 500ms;
}
&-exit {
opacity: 1;
}
&-exit&-exit-active {
opacity: 0;
transition: 500ms;
}
}
Then on the entry point index.jsx
:
<Router>
<Route path="/" component={ MyRandomComponent } /> // No more than one route
</Router>
See the final result here.
@stnwk Is Match
deprecated? I didn’t find it in the current v4.
@liushigit Yes, they changed the API over time. My code example is not api conform anymore. I'm sorry!
Please check out later comments or the official docs for a more recent implementation.
First of all: Thanks for the great new release! 😊
I am already heavily using it and look for a way to transition between routes. To be more precise:
How do I keep the children of one route still on screen while I prefetch data for rendering the following route after a click?
A short code-example would be nice, as the docs are still pretty fresh and don't include it. Thanks!