Component loading and transition animations with react-router - reactjs

I am using React, react-router and I am having page-like components. I'd like to implement two phase transition animations when the main view of the application changes
<Link> is clicked
Old (current) view fades out (component dismount?)
A spinner-like overlay animation "loading" is displayed
API call is triggered and the state is updated from the server (using AJAX)
State is updated
Spinner-like animation fades out
New component with freshly loaded state is fade in (component mount?)
I am looking to pointers what events, hooks and component method overloads I could use to implement this in generic manner for react-router and <Link>.

Perhaps you can try the:
1) onEnter
2) onLeave
Methods From react-router.
https://github.com/reactjs/react-router/blob/master/docs/API.md
You can flip a switch in state to change the UI to make spinner appear/disappear, etc. using the above two methods.

you can use ReactCSSTransitionGroup
import React from 'react'
import { render } from 'react-dom'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import { browserHistory, Router, Route, IndexRoute, Link } from 'react-router'
import './app.css'
const App = ({ children, location }) => (
<div>
<ul>
<li><Link to="/page1">Page 1</Link></li>
<li><Link to="/page2">Page 2</Link></li>
</ul>
<ReactCSSTransitionGroup
component="div"
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
{React.cloneElement(children, {
key: location.pathname
})}
</ReactCSSTransitionGroup>
</div>
)
const Index = () => (
<div className="Image">
<h1>Index</h1>
<p>Animations with React Router are not different than any other animation.</p>
</div>
)
const Page1 = () => (
<div className="Image">
<h1>Page 1</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing</p>
</div>
)
const Page2 = () => (
<div className="Image">
<h1>Page 2</h1>
<p>Consectetur adipisicing elit, se.</p>
</div>
)
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Index}/>
<Route path="page1" component={Page1} />
<Route path="page2" component={Page2} />
</Route>
</Router>
), document.getElementById('example'))
css
.Image {
position: absolute;
height: 400px;
width: 400px;
}
.example-enter {
opacity: 0.01;
transition: opacity .5s ease-in;
}
.example-enter.example-enter-active {
opacity: 1;
}
.example-leave {
opacity: 1;
transition: opacity .5s ease-in;
}
.example-leave.example-leave-active {
opacity: 0;
}
.link-active {
color: #bbbbbb;
text-decoration: none;
}

Related

Can't navigate to URL param using nested routes

I built a small example of nested routes using the useRoutes hook. I don't understand what I am doing different than the examples.
Here's a codesandbox.
I am unable to navigate to the :customerId URL param while in the 'customers' route.
import React from "react";
import { BrowserRouter, Link, RouteObject, useRoutes } from "react-router-dom";
const contentRoutes: RouteObject[] = [
{
element: <div>Home Page</div>,
index: true
},
{
element: (
<div>
Customers <Link to="microsoft">Microsoft</Link> (this is the link that doesn't work)
</div>
),
path: "customers",
children: [
{
path: ":customerId",
element: <div>Customer Microsoft</div>
}
]
}
];
const Content: React.FC = () => {
const content = useRoutes(contentRoutes);
return <div style={{ display: "flex" }}>{content}</div>;
};
export default function App() {
return (
<BrowserRouter>
<div className="App">
<div style={{ display: "flex", gap: "10px", marginBottom: '20px' }}>
<Link to="/">Home</Link>
<Link to="customers">Customers</Link>
</div>
<Content />
</div>
</BrowserRouter>
);
}
Looks like I haven't completely understood the concept of Outlet.
In the examples showing the Routes and Route React components, they use the Outlet component to display children routes.
In my example, I should just change to:
<div>
Customers <Link to="microsoft">Microsoft</Link>
<Outlet /> // HERE, use Outlet to paint children routes.
</div>

Using Fullpagejs with react-router-dom

I am building a website using Fullpage.js, React, and react-router-dom. I create a Link in the second section in the Fullpage component, and I want this Link can take me to another route (/anotherPage):
// Fullpage.js
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import { initFullpage } from "./initFullpage";
const Fullpage = () => {
useEffect(() => {
initFullpage();
}, []);
return (
<div id="fullpage">
<div class="section">Some section</div>
<div class="section">
<Link to="/anotherPage">
Go to a special page.
</Link>
</div>
<div class="section">Some section</div>
<div class="section">Some section</div>
</div>
);
};
export default Fullpage;
Additionally, I need another Link to go back from /anotherPage route back to the second section in the Fullpage component.
I made a demo here.
As you can try, if you click the link on the second section, react can route you to AnotherPage, but the navigation dots provide by Fullpage package is still there, and you can even scroll up and down, though this will cause errors. Also, if you click Go back, it will also break the App.
How to do it right? Thanks for your help!
Here is my App.js:
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Fullpage from "./components/Fullpage";
import AnotherPage from "./components/AnotherPage";
export default function App() {
return (
<Router>
<Switch>
<Route exact path="/">
<Fullpage />
</Route>
<Route exact path="/anotherPage">
<AnotherPage />
</Route>
</Switch>
</Router>
);
}
I found I don't need to use Fullpage.js to achieve the "scroll-full-page" effect. It's actually easy to achieve by CSS. Have a look at scroll-snap-type.
// index.html
<div class="container">
<section class="page">
Page one
</section>
<section class="page">
Page two
</section>
</div>
// css
.container{
width: 100%;
height: 100vh;
scroll-snap-type: y mandatory;
overflow: auto;
}
.page{
scroll-snap-align: start;
height: 100vh;
}
Then you can have Fullpage.js type scrolling effect. Then you can use react-router-dom without problem.

TransitionGroup component laggs on exit-active

I am implementing a slider for components rendered by the router. Created a component with TransitionGroup to automate appending of the classes by CSSTransition, and the code can be seen in a sandbox here. I have added 8 classes (2x enter, enter active, exit and exit active) to have left-right and right-left behavior ("Next" and "Back" buttons).
App.css file:
.slideIn-enter {
opacity: 0;
transform: translateX(-500px);
}
.slideIn-enter-active {
opacity: 1;
transition: all 600ms ease-out;
transform: translateX(0);
}
.slideIn-exit {
opacity: 1;
}
.slideIn-exit-active {
opacity: 0;
transition: all 600ms ease-in;
transform: translateX(500px);
}
/* */
.slideOut-enter {
opacity: 0;
transform: translateX(500px);
}
.slideOut-enter-active {
opacity: 1;
transition: all 600ms ease-in-out;
transform: translateX(0);
}
.slideOut-exit {
opacity: 1;
}
.slideOut-exit-active {
opacity: 0;
transition: all 600ms ease;
transform: translateX(-500px);
}
Slide component which renders the component that is routed to:
import React from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import { useLocation } from 'react-router-dom';
import './App.css';
export default function Slide(props) {
const location = useLocation();
return (
<>
<div className="box-container">
<TransitionGroup component={null}>
<CSSTransition
in={props.visible}
timeout={1200}
appear
classNames={props.isForward ? 'slideIn' : 'slideOut'}
unmountOnExit
key={location.key}
>
{props.children}
</CSSTransition>
</TransitionGroup>
</div>
</>
);
}
And App.js where the Slide component is wrapping a Switch which renders one of 2 routes with each press of the 2 buttons below:
import React, { useState } from 'react';
import Slide from './Slide';
import { Route, Switch } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import './App.css';
export default function App() {
const [isForward, setIsForward] = useState(true);
const history = useHistory();
const BoxRed = () => {
return <div className="box1">Box1</div>;
};
const BoxGreen = () => {
return <div className="box2">Box2</div>;
};
const pushNewRoute = () => {
if (history.location.pathname.endsWith('red')) {
history.push('/green');
} else {
history.push('/red');
}
};
return (
<>
<Route
render={({ location }) => (
<Slide isForward={isForward}>
<Switch location={location}>
<Route path="/green" component={BoxRed} />
<Route path="/red" component={BoxGreen} />
</Switch>
</Slide>
)}
></Route>
<div>
<button
className="btn-container"
onClick={() => {
setIsForward(true);
pushNewRoute();
}}
>
Next
</button>
<button
className="btn-container"
onClick={() => {
setIsForward(false);
pushNewRoute();
}}
>
Back
</button>
</div>
</>
);
}
In my index.js, I am wrapping the App.js in a BrowserRouter:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<BrowserRouter>
<Route path="/" component={App} />
</BrowserRouter>,
document.getElementById('root')
);
reportWebVitals();
Main issue, that can be seen in the codebox, is that the component stops when exiting component is in exit-active state (lets call it state) and the entering component is in enter-active state.
When entering component is set to enter-done, seems like it is translated half of its width more in the direction defined by the CSS and I cannot figure out why. I presumed the timeout on TransitionGroup was causing it, but whatever value greater than the transition time in CSS classes I'd put in, it just delayed the effect and cannot figure out why.
There is also a smaller issue with switching sets of classes which can be observed when going "Next" then "Back", but feel free to ignore it unless the issue is obvious (probably CSS classes aren't removed when appending new ones, but haven't had the time to tackle it). Thank you in advance!

React CSSTransition wrong class used on exit

UPDATE:
For those facing the same problem: I found a similar issue posted on the git page of ReactTransitionGroup with the solution in there: https://github.com/reactjs/react-transition-group/issues/182.
To be honest, 90% of the solution blew right over my head and I don't understand much of it at all, this is the only part that made sense to me(taken from React TransitionGroup and React.cloneElement do not send updated props which is linked the git issue):
Passing Props to Leaving Children?
Now the question is, why aren't updated props being passed to the leaving element? Well, how would it receive props? Props are passed from a parent component to a child component. If you look at the example JSX above, you can see that the leaving element is in a detached state. It has no parent and it is only rendered because the is storing it in its state.
I have to further research and understand the way parent and children components communicate. I'm still new to web development but things are slowly starting to make sense.
------------------
Initial question asked:
When you are attempting to inject the state to the children of your through React.cloneElement, the leaving component is not one of those children.
I have a small app that contains a nav menu and routes nested between a switch container. Every click on a nav item, I check if the index is higher or lower to set the animation state (Left or right) accordingly and then store the new index of the clicked nav item.
Everytime I switch routes, the animation applied is either fade-left or fade-right. Now this all works fine, but the only problem is when the class switches to right from left the EXIT animation applied uses the previous class given.
I understand why this is happening, It's because the exit class used is the one initially set, and when the new class is applied only on the second change does the new exit take affect. The transition documentation is very limited and I couldn't find anything.
const { Component } = React;
const { TransitionGroup, CSSTransition } = ReactTransitionGroup;
const { HashRouter, Route, NavLink, Switch, withRouter } = ReactRouterDOM;
var icons = [
{icon: 'far fa-address-book active', path: '/'},
{icon: 'fab fa-linkedin', path: '/linked'},
{icon: 'fas fa-gamepad', path: '/hobbies'}
];
const Nav = props => {
return (
<div className="nav">
<ul>
{
icons.map((icon, index) => {
return (
<li
key={index}
onClick={() => props.clicked(index)}
className={props.active == index ? 'active' : false}>
<NavLink to={icon.path} className="NavLink">
<i className={icon.icon} key={icon.icon}></i>
</NavLink>
</li>
);
})
}
</ul>
</div>
);
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentViewIndex: 0,
animationDirection: 'right'
}
this.setIndex = this.setIndex.bind(this);
}
setIndex(index){
const animationDirection = index < this.state.currentViewIndex ? 'left' : 'right';
this.setState({currentViewIndex: index, animationDirection});
}
render () {
const { location } = this.props;
return (
<div className="container">
<Nav active={this.state.currentViewIndex} clicked={() => this.setIndex()} />
<TransitionGroup>
<CSSTransition
key={location.pathname}
classNames={`fade-${this.state.animationDirection}`}
timeout={1000}>
<Switch location={location}>
<Route exact path="/" render={() => <h1>Home</h2>} />
<Route path="/linked" render={() => <h1>Linked</h2>} />
<Route path="/hobbies" render={() => <h1>Hobbies</h2>} />
</Switch>
</CSSTransition>
</TransitionGroup>
</div>
);
}
}
const AppWithRouter = withRouter(App);
ReactDOM.render(<HashRouter><AppWithRouter /></HashRouter>, document.querySelector('#root'));
The CSS classes applied to transitions:
.fade-left-enter {
position: unset;
transform: translateX(-320px);
}
.fade-left-enter.fade-left-enter-active {
position: unset;
transform: translateX(0);
transition: transform .5s ease-in;
}
.fade-left-exit {
position: absolute;
bottom: 0;
transform: translateX(0);
}
.fade-left-exit.fade-left-exit-active {
transform: translateX(320px);
transition: transform .5s ease-in;
}
.fade-right-enter {
position: unset;
transform: translateX(320px);
}
.fade-right-enter.fade-right-enter-active {
position: unset;
transform: translateX(0);
transition: transform .5s ease-in;
}
.fade-right-exit {
position: absolute;
bottom: 0;
transform: translateX(0);
}
.fade-right-exit.fade-right-exit-active {
transform: translateX(-320px);
transition: transform .5s ease-in;
}
The reason for your problem is that the exiting component is already detached and therefor does not get any updates. You can find a very good explanation of your problem here.
You can use the prop childFactory from <TransitionGroup> to solve this:
childFactory
You may need to apply reactive updates to a child as it is exiting.
This is generally done by using cloneElement however in the case of an
exiting child the element has already been removed and not accessible
to the consumer.
If you do need to update a child as it leaves you can provide a
childFactory to wrap every child, even the ones that are leaving.
Try the following code changes in your render method:
render () {
const { location } = this.props;
const classNames = `fade-${this.state.animationDirection}`; // <- change here
return (
<div className="container">
<Nav active={this.state.currentViewIndex} clicked={() => this.setIndex()} />
<TransitionGroup
childFactory={child => React.cloneElement(child, { classNames })} // <- change here
>
<CSSTransition
key={location.pathname}
classNames={classNames} // <- change here
timeout={1000}
>
<Switch location={location}>
<Route exact path="/" render={() => <h1>Home</h2>} />
<Route path="/linked" render={() => <h1>Linked</h2>} />
<Route path="/hobbies" render={() => <h1>Hobbies</h2>} />
</Switch>
</CSSTransition>
</TransitionGroup>
</div>
);
}

React Router v4: Animated transition jumps down

I try to setup fade-in fade-out transition between routed components using RR4 and ReactCSSTransitionGroup based on example from https://reacttraining.com/react-router/web/example/animated-transitions
Animations work, however previous component jumps down before next one is rendered.
What am I doing wrong and is there an easier way to transition between multiple components (without using parametrized paths, using react-motion instead of CSSTransitionGroup)?
screencast:
https://media.giphy.com/media/l4FGnVfAgiX4Hzns4/giphy.gif
code:
AnimatedTrans.js
import React from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import {MemoryRouter as Router, Redirect, Route, Link} from 'react-router-dom'
import s from './AnimatedTrans.css';
const DisplayParam = ({match: {params}}) => (
<div>param is: {params.pname} count is {params.count}</div>
);
const AnimatedTrans = () => (
<Router>
<Route render={({location}) => (
<div>
<Route exact path="/"
render={() => (<Redirect to='/home/0' />)} />
<ul>
<li><Link to="/home/1">Home</Link></li>
<li><Link to="/about/2">About</Link></li>
<li><Link to="/contact/3">Contact</Link></li>
</ul>
<div className={s.el}>
<ReactCSSTransitionGroup
transitionEnterTimeout={500}
transitionLeaveTimeout={200}
transitionName={{
enter: s.enter,
enterActive: s.eactive,
leave: s.leave,
leaveActive: s.lactive,
}}>
<Route location={location}
key={location.key}
path="/:pname/:count"
component={DisplayParam}
/>
</ReactCSSTransitionGroup>
</div>
</div>
)} />
</Router>
);
export default AnimatedTrans;
AnimatedTrans.css.
.el {
position: absolute;
}
.enter {
opacity: 0.01;
transition: opacity 0.3s ease-in 0.2s;
}
.eactive {
opacity: 1;
}
.leave {
opacity: 1;
transition: opacity 0.2s ease-in;
}
.lactive {
opacity: 0.01;
}
I have the feeling that you gave the absolute css property to the wrong div.
Instead of giving it to the <ReactCSSTransitionGroup/> parent div, try to give it to the children (and put the parent position as relative).

Resources