React CSSTransitionGroup with multiple components - css

I am trying to build a reusable animated overlay.
However, only the first component mount is detected, meaning that I can only use this overlay once for my whole application. If I use a second one, it won't be animated.
I tried a lot of thing, like using unique keys, transition names and so on, but I can't get this to work.
Here is the current code:
import React from 'react';
import { CSSTransitionGroup } from 'react-transition-group';
import styled from 'styled-componets'
const OverlayDiv = styled.div`
transition: opacity 300ms ease-in;
&._overlay-transition-appear,
&._overlay-transition-enter {
opacity: 0.01;
}
&._overlay-transition-appear._loading-overlay-transition-appear-active,
&._overlay-transition-enter._loading-overlay-transition-enter-active {
opacity: 1;
}
&._overlay-transition-leave {
opacity: 1;
}
&._overlay-transition-leave._loading-overlay-transition-leave-active {
opacity: 0;
}
`;
const animated = (WrappedComponent) => ({ animate = true, ...otherProps }) => {
if (animate) {
return (
<CSSTransitionGroup
transitionName="_overlay-transition"
transitionAppear
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
transitionAppearTimeout={500}
>
<WrappedComponent {...otherProps} />
</CSSTransitionGroup>
);
}
return <WrappedComponent {...otherProps} />;
};
export const Overlay = ({ animate = true, className, parentDimensions, children }) => (
<OverlayDiv
key="overlay" // import for React CSS Transition
className={className}
height={parentDimensions ? `${parentDimensions.offsetHeight}px` : '100%'}
>
{children}
</OverlayDiv>
);
export default animated(Overlay);
Any idea on this ?

Related

How can I modify the swiper.bundle.css?

I just added a swiper to my React project, and I need the pagination background to be transparent. The modifications I make on the CSS file don't seem to have any effect on the page itself.
So the first part is my JSX file, and the second is my CSS file. Both are linked correctly, I tried modifying the minified file but the problem remains the same.
import React, { useEffect, useState } from 'react';
import Game from '../Game';
import axios from 'axios';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation } from 'swiper';
import 'swiper/swiper-bundle.css';
import { Keyboard, Pagination, A11y } from 'swiper';
const GamesList = (props) => {
const { searchValue, setGameId, gameId, platform, genre } = props;
const [data, setData] = useState([]);
useEffect(() => {
const timeout = setTimeout(() => {
axios
.get(
`https://api.rawg.io/api/games?key=453247c1c78a4a88aa6594a59227801b&genres=${genre}&platforms=${platform}&search=${searchValue}`
)
.then((res) => {
setData(res.data.results);
});
}, 700);
return () => clearTimeout(timeout);
}, [searchValue, platform, genre]);
return (
<div id='list'>
{/* <p>{platformName}</p> */}
<Swiper
modules={[Navigation, A11y, Keyboard, Pagination]}
pagination
navigation
a11y
keyboard
effect
speed={800}
slidesPerView={3}
loop
className='my-swiper'
>
{data
.filter((gameChoice) => gameChoice.name.toLowerCase())
.map((game, index) => (
<SwiperSlide>
<Game/>
</SwiperSlide>
))}
</Swiper>
</div>
);
};
export default GamesList;
I tried adding the "background: none" line but nothing happened...
.swiper-pagination {
position: absolute;
text-align: center;
transition: 300ms opacity;
transform: translate3d(0, 0, 0);
z-index: 10;
background: none;
}

ReactTransitionGroup animation not playing on state change

I am trying to create an auto image slider with React and ReactTransitionGroup but I can't get animations to work. The image will change, but the transition effect won't play. I'm assuming it's the CSS because the class names do get added/removed.
Component
const ImgSlideshow = (props) => {
const images = [
'https://images.unsplash.com/photo-1593642634315-48f5414c3ad9?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80',
'https://images.unsplash.com/photo-1593642702821-c8da6771f0c6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80',
'https://images.unsplash.com/photo-1593642532781-03e79bf5bec2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=634&q=80',
'https://images.unsplash.com/photo-1593642634443-44adaa06623a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=925&q=80',
'https://images.unsplash.com/photo-1593642634524-b40b5baae6bb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80'
]
const [slide, setSlide] = useState(0);
useEffect(() => {
const slideshowTimer = setInterval(() => {
((slide + 1) > images.length - 1) ? setSlide(0) : setSlide(slide + 1)
}, 3000)
return () => clearInterval(slideshowTimer)
})
return (
<div className={styles.slideshow}>
<SwitchTransition>
<CSSTransition key={slide} timeout={200} classNames='slideshowImg'>
<img src={images[slide]} className="slideshowImg"></img>
</CSSTransition>
</SwitchTransition>
{props.children}
</div>
)
}
CSS
.slideshowImg-enter {
opacity: 0;
transform: translateX(-100%);
}
.slideshowImg-enter-active {
opacity: 1;
transform: translateX(0%);
}
.slideshowImg-exit {
opacity: 1;
transform: translateX(0%);
}
.slideshowImg-exit-active {
opacity: 0;
transform: translateX(100%);
}
.slideshowImg-enter-active,
.slideshowImg-exit-active {
transition: opacity 500ms, transform 500ms;
}
There are a couple of issues that I see.
You don't need to specify the className from the classNames prop on the child element that is being transitioned. If you have styles attached to this class then I would recommend you change it to something else to avoid confusion, but I don't think that is affecting your transitions.
You are setting transitions to 500ms but only allowing the transition group 200ms to complete the transition.
It also looks like you are just changing the image source instead of using a new element each time.
I have a working code sandbox here
I think you can achieve the desired result with this refactor:
const ImgSlideshow = (props) => {
const images = [
"https://images.unsplash.com/photo-1593642634315-48f5414c3ad9?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80",
"https://images.unsplash.com/photo-1593642702821-c8da6771f0c6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80",
"https://images.unsplash.com/photo-1593642532781-03e79bf5bec2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=634&q=80",
"https://images.unsplash.com/photo-1593642634443-44adaa06623a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=925&q=80",
"https://images.unsplash.com/photo-1593642634524-b40b5baae6bb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80"
];
const [slide, setSlide] = useState(0);
const imageElements = images.map((img, i) => {
return <img src={img} key={i} alt={`slideshow ${i+1}`} />;
});
useEffect(() => {
const slideshowTimer = setInterval(() => {
slide + 1 > images.length - 1 ? setSlide(0) : setSlide(slide + 1);
}, 3000);
return () => clearInterval(slideshowTimer);
});
return (
<div>
<SwitchTransition>
<CSSTransition key={slide} timeout={500} classNames="slideshowImg">
{imageElements[slide]}
</CSSTransition>
</SwitchTransition>
{props.children}
</div>
);
};
My snippet uses the map function to create an array of img elements, each with a src that is correspondent to the index of the images array. This way you will have multiple elements to transition in between. Right now you only have one image element, and the react-transition-group can't put an animation on the changing of the image src (afaik).

"appear" in CSSTransition doesn't work as expected

I'd like to implement CSSTransition to animate an element but still mount it right away.
Based on the documentation:
By default the child component does not perform the enter transition when it first mounts, regardless of the value of in. If you want this behavior, set both appear and in to true.
So I would think that this example would work:
<CSSTransition
in={showMessage}
appear={showMessage}
timeout={300}
classNames="alert"
>
.alert-enter,
.alert-appear {
opacity: 0;
transform: scale(0.9);
}
.alert-enter-active,
.alert-appear-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.alert-exit {
opacity: 1;
}
.alert-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}
Here is a codesandbox: https://codesandbox.io/s/keen-forest-ghgxq?file=/index.js
As you can see it is displayed right away. I need it to be hidden but still mounted. How do I do this?
But you didn't setup the show property of the Alert component. So by default, it is true
Here's how you can hide it in the beginning, then show and unshow, sandbox
Code:
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import { Container, Button, Alert } from 'react-bootstrap';
import { CSSTransition } from 'react-transition-group';
import './styles.css';
function Example() {
const [showMessage, setShowMessage] = useState(false);
useEffect(() => {
setTimeout(() => {
setShowMessage(true);
}, 10000);
}, []);
return (
<Container style={{ paddingTop: '2rem' }}>
<CSSTransition
in={showMessage}
exit={showMessage}
appear={showMessage}
timeout={300}
classNames="alert"
>
<Alert
variant="primary"
dismissible
show={showMessage}
onClose={() => setShowMessage(false)}
>
<Alert.Heading>
Animated alert message
</Alert.Heading>
<p>
This alert message is being transitioned in and
out of the DOM.
</p>
<Button onClick={() => setShowMessage(false)}>
Close
</Button>
</Alert>
</CSSTransition>
<button onClick={() => setShowMessage(true)}>
Show me
</button>
</Container>
);
}
ReactDOM.render(
<Example />,
document.getElementById('root')
);

React CSSTransition Issues

I am trying to display header one in the beginning and then when clicked on, fade in the second header. However they both are showing up in beginning. My understanding is that the in prop is what controls when the children of get rendered. But for some reason they are being rendered in beginning when showTwo is false. Any help would be much appreciated.
Component
import React, { useState } from "react";
import { CSSTransition } from "react-transition-group";
import "./Intro.css";
function Intro() {
const [showOne, setShowOne] = useState(true);
const [showTwo, setShowTwo] = useState(false);
return (
<>
{showOne && <h1 onClick={() => setShowTwo(true)}>One!</h1>}
<CSSTransition
in={showTwo}
timeout={750}
onEnter={() => setShowOne(false)}
classNames="creation"
>
<h2>Two!</h2>
</CSSTransition>
</>
);
}
export default Intro;
CSS
.creation-enter {
opacity: 0;
}
.creation-enter-active {
opacity: 1;
transition: opacity 700ms;
}
.creation-exit {
opacity: 1;
}
.creation-exit-active {
opacity: 0;
transition: opacity 700ms;
}
I realized that I was missing the prop unmountOnExit and misunderstanding what the in prop is for.
If others stumble upon this with similar issues, in is used to determine when the *-enter, *-enter-active, ... are added as classes.

Using ReactCSSTransitionGroup with styled-component

I'm using styled-components instead of tradition way of css. But I don't know how it can work together with ReactCSSTransitionGroup.
Basically, ReactCSSTransitionGroup looks for certain classnames in css resource, then apply to a component throughout its lifecycle. However, with styled-components, there are not any class names, styles are applied to components directly.
I know I can choose not to use ReactCSSTransitionGroup because the two technique doesn't look compatible. But when I use only styled-components, seems I can't render any animation when a component is unmounted - it's pure css, can't access component's lifecycle.
Any help or recommendation is appreciated.
I didn't want to use injectGlobal as suggested in another answer because I needed to make the transitions different per component.
It turns out to be pretty easy - just nest the transition classes in the styling for the component:
import React from "react";
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
import styled from 'styled-components';
const appearDuration = 500;
const transitionName = `example`;
const Container = styled.section`
font-size: 1.5em;
padding: 0;
margin: 0;
&.${transitionName}-appear {
opacity: 0.01;
}
&.${transitionName}-appear-active {
opacity: 1;
transition: opacity ${appearDuration}ms ease-out;
}`;
export default () => {
return (
<CSSTransitionGroup
transitionName={transitionName}
transitionAppear={true}
transitionAppearTimeout={appearDuration}>
<Container>
This will have the appear transition applied!
</Container>
</CSSTransitionGroup>
);
};
Note that I'm using the newer CSSTransitionGroup, rather than ReactCSSTransitionGroup, but it should work for that too.
Mike Goatly's approach is great, but I had to make small changes to make it work. I changed the <CSSTransition>'s props, and used a function as its child.
See below for an example of a component, which fades in/out based on a state change:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { CSSTransition } from "react-transition-group";
import styled from "styled-components";
const Box = styled.div`
width: 300px;
height: 300px;
background: red;
transition: opacity 0.3s;
// enter from
&.fade-enter {
opacity: 0;
}
// enter to
&.fade-enter-active {
opacity: 1;
}
// exit from
&.fade-exit {
opacity: 1;
}
// exit to
&.fade-exit-active {
opacity: 0;
}
}`;
export default class App extends Component {
constructor() {
super();
this.state = {
active: true
};
setInterval(() => this.setState({ active: !this.state.active }), 1000);
}
render() {
return (
<CSSTransition
in={this.state.active}
classNames="fade"
timeout={300}
unmountOnExit
>
{() => <Box />}
</CSSTransition>
);
}
}
You can use css variable selector in styled-components. Like this:
const Animation = styled(ReactCSSTransitionGroup)`
${({ transitionName }) => `.${transitionName}-enter`} {
opacity: 0;
}
${({transitionName}) => `.${transitionName}-leave`} {
opacity: 1;
}
`
const animationID = 'some-hashed-text'
const AnimationComponent = props => (
<Animation
transitionName={animationID}
transitionEnterTimeout={0.1}
transitionLeaveTimeout={2000}
>
<div>some content</div>
</Animation>
)
Use the injectGlobal() styled-component helper method where your React app is bootstrapped. With this method you can style any CSS selector as if you'd be using conventional CSS.
First create a JS file exporting a template literal with your CSS for the react-transition-group (please not I'm using v2.1 new class names syntax):
globalCss.js
const globalCss = `
.transition-classes {
/* The double class name is to add more specifity */
/* so that this CSS has preference over the component one. */
/* Try removing it, you may not need it if properties don't collide */
/* https://www.styled-components.com/docs/advanced#issues-with-specificity */
&-enter&-enter {
}
&-enter&-enter-active {
}
&-exit&-exit {
}
&-exit&-exit-active {
}
}
`;
export default globalCss;
Then on your entry point file:
index.jsx
import { injectGlobal } from "styled-components";
import globalCss from "./globalCss.js";
injectGlobal`${ globalCss }`; // <-- This will do the trick
ReactDOM.render(
<Provider store={ Store } >
<HashRouter >
<Route path="/" component={ Component1 } />
<Route path="/" component={ Component2 } />
</HashRouter>
</Provider>,
document.getElementsByClassName("react-app")[0]
);
However, if you just use CSS/SASS/Less to write the classes for the react-trasition-group even when you use styled-components, it also works well.
There is a great blog post explaining how to do this:
https://dev.to/terrierscript/styled-component--react-transition-group--very-simple-transition-jja
They use a low level Transiton component available from react-transition-group:
http://reactcommunity.org/react-transition-group/transition
// This is overly simplified, but styles change depend on state from Transition
const MyStyledComponent = styled.div`
transform: translateY(${({ state }) => (state === 'exited' ? "0" : "-100%")});
transition: transform 2s;
`
const App = () =>
<Transition in={animate} timeout={500}>
{(state) => (
// state change: exited -> entering -> entered -> exiting -> exited
<MyStyledComponent state={state}>Hello</MyStyledComponent>
)}
</Transition>
import React from "react";
import { CSSTransition } from 'react-transition-group';
const styles = theme => ({
'fade-enter':{
opacity: 0,
},
'fade-enter-active':{
opacity: 1,
transition: "opacity 300ms"
},
'fade-exit':{
opacity: 1,
},
'fade-exit-active':{
opacity: 0,
transition: "opacity 300ms"
},
})
class myAnimatedComponent extends React.Component {
constructor(props){
super(props);
}
render(){
let {classes} = this.props;
return (
<CSSTransition
in={this.props.conditionVariable}
classNames={{
enter: classes['fade-enter'],
enterActive: classes['fade-enter-active'],
exit: classes['fade-exit'],
exitActive: classes['fade-exit-active'],
}}
timeout={300}
unmountOnExit>
<span>This will have the transition applied to it!</span>
</CSSTransition>
);
}
};
export default (styles)(myAnimatedComponent);
I had to use classes['fade-enter'] etc, because React changes the name of all classes in this component due to the fact that I used withStyles. And because of that too, when I export the component, React inserts my classes into this component's props, that's why I also had to create a variable called classes to catch those classes.

Resources