React CSSTransition wrong class used on exit - reactjs

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>
);
}

Related

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!

Change hover style under custom component in styled-component

I have a below structure.
<Nav>
<Title/>
<DropDown />
</Nav>
<Nav /> is a class component and I need to show Dropdown when I hover over <Nav />.
Here is the code snippet of <Nav />.
export default class HeaderLink extends React.PureComponent {
...
}
Here is the code snippet of <DropDown />.
const Container = styled.ul`
opacity: 0;
visibility: hidden;
transform: translateY(20px);
transition: all .3s ease-in-out;
${Nav}:hover & {
opacity: 1;
visibility: visible;
transform: translateY(-2px);
}
`;
const DropDown = ({ items }) => (
<Container>
{items.map(({ title, url }) => (
<a href={url}>{title}</a>
))}
</Container>
);
DropDown.propTypes = {
items: PropTypes.array.isRequired
};
export default DropDown;
This is not working but I figured that If I define <Nav /> component as a styled-component, it works
i.e. const Nav = styled.ul''
But it's not working for the class component.
Any thoughts on this?
Thanks.
You're attempting to use a parent as a selector, which is not currently possible in CSS (see: Is there a CSS parent selector?). Your :hover should be on your Nav component, which in turn targets the appropriate child element.
See example CodeSandbox here: https://codesandbox.io/s/x9lmkply4.

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).

React Router v4 Animated Transition Example

The animation transition example provided in the v4 docs seems a little convoluted to me, since it depicts fading the same component in and out and adjusting the background color.
I'm trying to apply this technique to a more real-world example of fading one component out and another in, however I can't get it to work properly (it only seems to fade the first one out, then the second one pops in, and this transition only works one way (back button results in no transition).
Here's my code, which uses a stripped down version of MatchWithFade from the example:
import React from 'react';
import { TransitionMotion, spring } from 'react-motion'
import { HashRouter, Match, Miss } from 'react-router';
import Home from './components/Home';
import Player from './components/Player';
import FormConfirmation from './components/FormConfirmation';
const App = () => (
<HashRouter>
<div className="App">
<MatchWithFade exactly pattern="/" component={Home} />
<MatchWithFade pattern="/player/:playerId" component={Player} />
<MatchWithFade pattern="/entered" component={FormConfirmation} />
<Miss render={(props) => (
<Home players={Players} prizes={Prizes} {...props} />
)} />
</div>
</HashRouter>
);
const MatchWithFade = ({ component:Component, ...rest }) => {
const willLeave = () => ({ zIndex: 1, opacity: spring(0) })
return (
<Match {...rest} children={({ matched, ...props }) => (
<TransitionMotion
willLeave={willLeave}
styles={matched ? [ {
key: props.location.pathname,
style: { opacity: spring(1) },
data: props
} ] : []}
>
{interpolatedStyles => (
<div>
{interpolatedStyles.map(config => (
<div
key={config.key}
style={{...config.style}}
>
<Component {...config.data}/>
</div>
))}
</div>
)}
</TransitionMotion>
)}/>
)
}
export default App;
I realize that this question is nearly a duplicate of this one, however that one has an accepted answer that doesn't actually answer the question.
react-motion is not good for large applications. Use react-transition-group instead.
Because react-motion use javascript to perform the transition. When making API call the transition will cross path(sync call in js) and make the transition laggy when route to new page. Where as react-transition-group use CSS to perform the transition.
The v4 docs have moved to react-transition-group so you may consider to do the same.
With regards to "fading the same component in and out" it's not really the same instance of the Component. Two instances exist simultaneously and can provide the cross-fade. react-motion docs says "TransitionMotion has kept c around" and I imagine react-transition-group is the same.
Here's an updated solution using react 16 and ReactCSSTransitionGroup from "react-addons-css-transition-group"
index.js
import React, { Component } from "react";
import ReactCSSTransitionGroup from "react-addons-css-transition-group";
import { render } from "react-dom";
import "./main.css";
function* continuosArrayIterator(arr) {
let idx = 0;
while (idx < arr.length) {
let ret = arr[idx];
idx++;
if (idx === arr.length) {
idx = 0;
}
yield ret;
}
}
class App extends Component {
constructor() {
super();
this.clickHandler = this.clickHandler.bind(this);
this.items = [
{
id: 1,
text: "item1",
img: "https://mirrors.creativecommons.org/presskit/icons/cc.large.png"
},
{
id: 2,
text: "item2",
img: "https://mirrors.creativecommons.org/presskit/icons/by.large.png"
},
{
id: 3,
text: "item3",
img: "https://mirrors.creativecommons.org/presskit/icons/nc.large.png"
},
{
id: 4,
text: "item4",
img:
"https://mirrors.creativecommons.org/presskit/icons/nc-eu.large.png"
}
];
this.imageIterator = continuosArrayIterator(this.items);
this.state = {
image: this.imageIterator.next().value
};
}
clickHandler(event) {
return this.setState({
image: this.imageIterator.next().value
});
}
render() {
return (
<div>
<button onClick={this.clickHandler}>Next Image</button>
<ReactCSSTransitionGroup
transitionAppear={true}
transitionLeaveTimeout={500}
transitionEnterTimeout={500}
className="container"
transitionName="example"
>
<div
key={this.state.image.id}
style={{
position: "absolute",
backgroundImage: `url(${this.state.image.img}`,
backgroundSize: "auto 100px",
height: "100px",
width: "100px"
}}
/>
</ReactCSSTransitionGroup>
</div>
);
}
}
render(<App />, document.getElementById("root"));
main.css
.container {
position: absolute;
}
.example-enter {
opacity: 0.01;
}
.example-enter.example-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.example-leave {
opacity: 1;
}
.example-leave.example-leave-active {
opacity: 0.01;
transition: opacity 500ms ease-in;
}
.example-appear {
opacity: 0.01;
}
.example-appear.example-appear-active {
opacity: 1;
transition: opacity 0.5s ease-in;
}
I didn't like the RRv4 example for animiations either. I ended up writing my own "AnimatedSwitch" for RRv4 Native.
I imagine it wouldn't take too much work to swap out the native animations for react-spring and be up and running.
https://javascriptrambling.blogspot.com/2020/07/react-router-native-animatedswitch.html

Component loading and transition animations with react-router

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;
}

Resources