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
Related
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!
I am having difficulties with my react application. I have a component that cyclically calls iframes, making them visible for a few seconds. The "reload" takes place via a button.
However, I need to check the iframe reload from another page. For example, create a "configuration.js" component in which there is a button which, once clicked, activates the "reload" function.
The iframe reload is however rendered in "http: // localhost: 3000 / simulatoretv", while the button that starts the reload is present in "http: // localhost: 3000 / configurator".
I hope I've been sufficiently clear. I attach the script of the component "Advertising", through which I run the reload of the iframes.
import React, { Component } from "react";
class Pubblicità extends Component {
state = {
index: 0,
iframeSrcs: ["/300x250", "/160x600", "/468x60"],
visibility: false
};
reload = () => {
const iframeLength = this.state.iframeSrcs.length;
if (this.state.index < iframeLength) {
this.setState({
index: this.state.index + 1,
visibility: true
});
} else {
this.setState({ index: 0, visibility: true }); //starting again
}
setTimeout(() => {
this.setState({ visibility: false });
}, 15000);
};
render() {
return (
<div>
<button
style={{
position: "absolute",
left: 0,
right: 0,
top: 500
}}
onClick={this.reload}
>
pubblicità
</button>
{this.state.visibility ? (
<iframe
style={{
position: "absolute",
left: 500,
right: 0,
top: 10
}}
key={this.state.index}
title="AdSlot"
src={this.state.iframeSrcs[this.state.index]}
height="100%"
width="100%"
scrolling="no"
frameborder="0"
/>
) : (
""
)}
</div>
);
}
}
export default Pubblicità;
This is the other component, where I would like the controlled iframes to be shown by the "Pubblicità" component.
The route of this component is "http: // localhost: 3000 / platformOTT".
import React, { Component } from "react";
import SimulatoreTV from "./SimulatoreTV";
import Pubblicità from "./Pubblicità";
class PiattaformaOTT extends Component {
render() {
return (
<div>
<SimulatoreTV />
<Pubblicità />
</div>
);
}
}
export default PiattaformaOTT;
Thanks to those who want to help me.
I am using react-custom-scrollbars in a react web app because I need to have two independant scroll bars, one for the main panel and one for the drawer panel. My issue is that the content in the main panel is dynamic and whenever I take some action in the main panel that changes state the scroll bar jumps to the top of the panel again.
UPDATE:
I believe I need to list for onUpdate and handle the scroll position there. If it has changed then update if not do not move the position?
In code, I have a HOC call withScrollbar as follows:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Scrollbars } from 'react-custom-scrollbars';
import { colors } from '../../theme/vars';
import { themes } from '../../types';
// This is a Higher Order Component (HOC) used to
// provide a scroll bar to other components
export default (ChildComponent, styling) => {
class ComposedComponent extends Component {
state = {
// position: ??
};
handleUpdate = () => {
//update position
//this.scrollbar.scrollToBottom();
};
render() {
return (
<AutoSizer>
{
({ width, height }) => (
<Scrollbars
style={{ width, height, backgroundColor: colors.WHITE, overflow: 'hidden', ...styling }}
onUpdate={() => this.handleUpdate()}
renderThumbVertical={props => <Thumb {...props} />}
autoHide
autoHideTimeout={1000}
autoHideDuration={200}
>
<ChildComponent {...this.props} />
</Scrollbars>
)
}
</AutoSizer>
);
}
}
return ComposedComponent;
};
const Thumb = styled.div`
background-color: ${props =>
props.theme.theme === themes.LIGHT ? colors.BLACK : colors.WHITE};
border-radius: 4px;
`;
in my MainView component I just wrap the export like this:
export default withScrollbar(LanguageProvider(connect(mapStateToProps, null)(MainView)));
I have read a few similar issues on this like this one: How to set initial scrollTop value to and this one scrollTo event but I cannot figure out how to implement in my case. Any tips or suggestions are greatly appreciated.
So I found a way to get this to work and it feels like a complete hack but I'm posting in hopes it might help someone else.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Scrollbars } from 'react-custom-scrollbars';
import { colors } from '../../theme/vars';
import { themes } from '../../types';
// This is a Higher Order Component (HOC) used to
// provide a scroll bar to other components
export default (ChildComponent, styling) => {
class ComposedComponent extends Component {
state = {
stateScrollTop: 0,
};
onScrollStart = () => {
if (this.props.childRef) { // need to pass in a ref from the child component
const { scrollTop } = this.props.childRef.current.getValues();
const deltaY = Math.abs(scrollTop - this.state.stateScrollTop);
if (deltaY > 100) { // 100 is arbitrary. It should not be a high value...
this.props.childRef.current.scrollTop(this.state.stateScrollTop);
}
}
};
handleUpdate = () => {
if (this.props.childRef) {
const { scrollTop } = this.props.childRef.current.getValues();
this.setState({ stateScrollTop: scrollTop });
}
};
render() {
return (
<AutoSizer>
{
({ width, height }) => (
<Scrollbars
ref={this.props.childRef}
style={{ width, height, backgroundColor: colors.WHITE, overflow: 'hidden', ...styling }}
onScrollStart={e => this.onScrollStart(e)}
onUpdate={e => this.handleUpdate(e)}
renderThumbVertical={props => <Thumb {...props} />}
autoHide
autoHideTimeout={1000}
autoHideDuration={200}
>
<ChildComponent {...this.props} />
</Scrollbars>
)
}
</AutoSizer>
);
}
}
return ComposedComponent;
};
const Thumb = styled.div`
background-color: ${props =>
props.theme.theme === themes.LIGHT ? colors.BLACK : colors.WHITE};
border-radius: 4px;
`;
I use this HOC like this:
create a ref for the component you want to use it with
pass the ref to the component that will use the HOC:
class SomeChildComponent extends Component {
...
viewRef = React.createRef();
...
render() {
return ( <MainView childRef={this.viewRef} />)
}
import and wrap the component
import withScrollbar from '../../hoc/withScrollbar';
...
export default withScrollbar(MainView);
I tried the above solution and it didn't seem to work for me.
However what did work was making sure that my child components inside Scrollbars were wrapped in a div with a height of 100%:
<Scrollbars>
<div style={{ height: '100%' }}>
<ChildComponent />
<ChildComponent />
</div>
</Scrollbars>
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>
);
}
I'm building a "hovering" menu, using semantic-ui-react's Popup, and want to add a simple show-hide transition, how it can be done?
this is probably coming in too late for this specific OP, but might be useful for someone else trying to figure same out.
I believe you can use the TransionablePortal as shown in the example. Just for fun, I adapted that example to what I think you are trying to do:
import React, { Component } from 'react'
import { Button, Menu, TransitionablePortal } from 'semantic-ui-react'
export default class TransitionablePortalExamplePortal extends Component {
state = { open: false }
handleOpen = () => this.setState({ open: true })
handleClose = () => this.setState({ open: false })
render() {
const { open } = this.state
return (
<TransitionablePortal
closeOnTriggerClick
onOpen={this.handleOpen}
onClose={this.handleClose}
transition={{animation: "fade left", duration: 500 }}
openOnTriggerClick
trigger={
<Button circular basic
icon="ellipsis vertical"
negative={open}
positive={!open}
/>
}
>
<Menu vertical style={{ right: '1%', position: 'fixed', top: '0%', zIndex: 1000}}>
<Menu.Item>Menu Item 1</Menu.Item>
<Menu.Item>Menu Item 2</Menu.Item>
</Menu>
</TransitionablePortal>
)}}
You should be able to make the transition use onMouseEnter and onMouseLeave if you want same transition to be on hover, instead of on click.
You can find in their official documentation example where you can make custom style for popup
import React from 'react'
import { Button, Popup } from 'semantic-ui-react'
const style = {
borderRadius: 0,
opacity: 0.7,
padding: '2em',
}
const PopupExampleStyle = () => (
<Popup
trigger={<Button icon='eye' />}
content='Popup with a custom style prop'
style={style}
inverted
/>
)
export default PopupExampleStyle
You can try to add transition property here