React animate with CSSTransitionGroup on toggle - javascript

I am trying to get a transition between components entering and leaving when the button is clicked to toggle between state
I have also tried putting <UserDetails /> and <UserEdit /> as seperate components but have the same result in that no animation is triggered.
https://www.webpackbin.com/bins/-KjIPcMeQF3iriHRqTBW
Hello.js
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'
import './styles.css'
export default class Hello extends React.Component {
constructor() {
super()
this.state = { showEdit: false }
this.handleEdit = this.handleEdit.bind(this)
}
handleEdit() { this.setState({ showEdit: !this.state.showEdit }) }
render() {
const UserDetails = () => (
<div className="componenta" key="1">UserDetails</div>
)
const UserEdit = () => (
<div className="componentb" key="2">UserEdit</div>
)
return(
<div >
<CSSTransitionGroup
transitionName="example"
transitionEnterTimeout={10}
transitionLeaveTimeout={600}>
{this.state.showEdit ? <UserDetails /> : <UserEdit />}
</CSSTransitionGroup>
<button onClick={this.handleEdit}>Toggle</button>
</div>
)
}
}
styles.css
.thing-enter {
opacity: 0.01;
transition: opacity 1s ease-in;
}
.thing-enter.thing-enter-active {
opacity: 1;
}
.thing-leave {
opacity: 1;
transition: opacity 1s ease-in;
}
.thing-leave.thing-leave-active {
opacity: 0.01;
}

You have your key attribute set to the wrong item - the <div> is not the direct descendant of CSSTransitionGroup (because your wrapped it into a component by defining it as a function), so it doesn't know which items were added or removed. You have to set your keys to UserDetails and UserEdit, so CSSTransitionGroup can properly determine changes in it's children.
Here is your render method that works:
render() {
const UserDetails = () => (
<div className="componenta">UserDetails</div>
)
const UserEdit = () => (
<div className="componentb">UserEdit</div>
)
return(
<div >
<CSSTransitionGroup
transitionName="example"
transitionEnterTimeout={10}
transitionLeaveTimeout={600}>
{this.state.showEdit ? <UserDetails key="1" /> : <UserEdit key="2" />}
</CSSTransitionGroup>
<button onClick={this.handleEdit}>Toggle</button>
</div>
)
}
An alternative would be to store UserDetails and UserEdit into variables and then render them as such. That will allow you to leave the key attributes where they are now, instead of moving them.

Here is a working example using display:none to hide the unmounting component
enter link description here

Related

How to display toast using react-toastify inside class component when props check is true

I am working on react project where i am trying to display toast using react-toastify , inside a div section when props ( isNotificationOpen ) is true. I tried an example something like bellow but i dont want the toast be triggered when button press occurs , i want the the tost to be triggered when isNotificationOpen props is set to true , how can i achieve this?
const notify = () => toast("Wow so easy !");
render() {
return (
<div className="d-flex" style={{ margin: "25px" }}>
<div className="mr-auto p-2">
{this.props.isNotificationOpen ? (
<div>
<div>
<button onClick={notify}>Notify !</button>
<ToastContainer />
</div>
//Show toast here...
</div>
) : (
<p />
)} ```
Use a component lifecycle function to respond to the isNotificationOpen prop changing to trigger a notfication.
Class-based component example
notify = () => toast('Wow so easy!');
componentDidMount() {
const { isNotificationOpen } = this.props;
isNotificationOpen && this.notify();
}
componentDidUpdate(prevProps) {
const { isNotificationOpen } = this.props;
if (prevProps.isNotificationOpen !== isNotificationOpen) {
isNotificationOpen && this.notify();
}
}
Functional component example
useEffect(() => {
props.isNotificationOpen && toast('Wow so easy!');
}, [props.isNotificationOpen]);

Sidebar transition with React and Redux?

So I have a SideDrawer that I'm rendering like this in the root App component:
render() {
const { isOpen } = this.props;
return (
<BrowserRouter>
<SideDrawer />
{/* isOpen && <SideDrawer /> */}
<Routes ..../>
</BrowserRouter>
);
}
}
This is the reducer:
export const drawer = (state = initialState, { type, payload }) => {
switch (type) {
case CLOSE_DRAWER:
return { ...state, isOpen: false };
case OPEN_DRAWER:
return { isOpen: true, movieId: payload };
default:
return state;
}
};
And In the SideDrawer component, I was using the state of the drawer and css to handle the transition:
export const SideDrawer = ({ isOpen, closeDrawer, movieId }) => {
const drawerClass = cx({
container: true,
open: isOpen,
});
return (
<div className={drawerClass}>
<button className={styles.button} onClick={closeDrawer}>
<img src={backButton} alt={text.alt} />
</button>
<MovieBanner movieId={movieId} />
</div>
);
};
Now, my mentor wants me to render the SideDrawer conditionally. If isOpen is true, then render the SideDrawer. However, the problem is that the transition is not going to work, because I'd only be rendering the SideDrawer when the state of isOpen is true. The only way it's working now is if I just render it all the time on the App component so I can keep track of the state and add my CSS transition when the state changes from false to true
My mentor mentioned to only render the SideDrawer if the SideDrawer is open, but I need to have it rendered to know the previous state of it no?
When an element is removed from the DOM, it will no longer be shown to the user. This also means transitions on it will have no impact.
If you would like to use transitions, keep the element in the DOM at least until the transitions have finished, or always. Here is a demo of two <div>s, first one is always in the DOM, the second one only when sidebarOn is true:
function Demo() {
const [sidebarOn, setSidebarOn] = React.useState(false)
const toggleSidebar = () => setSidebarOn(!sidebarOn)
return (
<div>
<button onClick={toggleSidebar}>toggle</button>
<div className={'sidebar ' + sidebarOn}>always rendered</div>
{sidebarOn &&
<div className={'sidebar ' + sidebarOn}>only rendered when on</div>
}
</div>
)
}
ReactDOM.render(<Demo />, document.getElementById('root'))
.sidebar {
background-color: yellow;
opacity: 0;
transition-property: opacity;
transition-duration: 500ms;
}
.sidebar.true {
opacity: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
There exists a transitionend event, which you could use to remove the element once the transition is over, but support in browsers is not widespread enough at this time.

How to add classNames in conditional rendering in reactjs?

I have a list of data with images. I want to make image carousel. For this I have created card component and I want here to display 4 cards at a time and remaining should be hidden. Then i want to setTimeout of 5s to display remaining but only for at a time.
So far I have done this.
about.js
import './about.scss';
import data from '../../data/data';
import CardSection from './card';
class About extends React.Component{
constructor(props){
super(props);
this.state = {
properties: data.properties,
property: data.properties[0]
}
}
nextProperty = () => {
const newIndex = this.state.property.index+4;
this.setState({
property: data.properties[newIndex]
})
}
prevProperty = () => {
const newIndex = this.state.property.index-4;
this.setState({
property: data.properties[newIndex]
})
}
render() {
const {property, properties} = this.state;
return (
<div className="section about__wrapper">
<div>
<button
onClick={() => this.nextProperty()}
disabled={property.index === data.properties.length-1}
>Next</button>
<button
onClick={() => this.prevProperty()}
disabled={property.index === 0}
>Prev</button>
<Container className="card__container">
<div class="card__main" style={{
'transform': `translateX(-${property.index*(100/properties.length)}%)`
}}>
{
this.state.properties.map(property => (
<CardSection property={property}/>
))
}
</div>
</Container>
</div>
</div>
)
}
}
export default About
about.scss
.card__container{
overflow-x: hidden;
}
.card__main{
display: flex;
position: absolute;
transition: transform 300ms cubic-bezier(0.455, 0.03, 0.515, 0.955);
.card__wrapper {
padding: 20px;
flex: 1;
min-width: 300px;
}
}
card.js
import React from "react";
import { Card, CardImg, CardText, CardBody,
CardTitle, CardSubtitle, Button } from 'reactstrap';
class CardSection extends React.Component {
render() {
return (
<div className="card__wrapper">
<Card>
<CardImg top width="100%" src={this.props.property.picture} alt="Card image cap" />
<CardBody>
<CardTitle>{this.props.property.city}</CardTitle>
<CardSubtitle>{this.props.property.address}</CardSubtitle>
<CardText>Some quick example text to build on the card title and make up the bulk of the card's content.</CardText>
<Button>Button</Button>
</CardBody>
</Card>
</div>
);
}
}
export default CardSection;
I have added transition in them to change card onclick but i want them to auto change and hide the remaining card.
Right now it looks like this,
You can add items in componentDidMount method using setInterval
componentDidMount() {
this.interval = setInterval(() => this.setState({
properties:data.properties /* add your data*/ }), 4000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
You can have a property called showCardIds that holds an array of the Id of cards that need to be shown, and use that to set a Boolean property called hidden on the div of the card.
You could also do something like this as shown in the example below, this example also uses showCardIds as a state. It filters only for the property that needs to be rendered and filters out the rest.
Here is an example:
...
{
this.state.properties.filter((property, index) => showCardIds.includes(index)).map(property => (
<CardSection property={property}/>
))
}
...
That way only the ones that are present in the array of showCardIds would show up, there needs to be more logic to be written that would populate the ids in showCardIds
Hope this helps. The hidden property is supported from HTML5, and should work on most browsers, unless they are truly "ancient".

Conditional Rendering and ReactCSSTransitionGroup Animation

I've made a small app that renders different components based on a Redux state. I want to apply a "fade" animation when one of the component renders. However, for some reason, it doesn't work for me. Here's what I have so far:
content.js
class Content extends Component {
render() {
const transitionOptions = {
transitionName: "fade",
transitionEnterTimeout: 500,
transitionLeaveTimeout: 500
}
if (this.props.page === 'one') {
return (
<div>
<ReactCSSTransitionGroup {...transitionOptions}>
<Comp1/>
</ReactCSSTransitionGroup>
</div>
);
} else {
return (
<div>
<ReactCSSTransitionGroup {...transitionOptions}>
<Copm2/>
</ReactCSSTransitionGroup>
</div>
);
}
}
}
style.css
.fade-enter {
opacity: 0.01;
}
.fade-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.fade-leave {
opacity: 1;
}
.fade-leave.fade-leave-active {
opacity: 0.01;
transition: opacity 300ms ease-in;
}
I've seen ReactCSSTransitionGroup being used for items being added and removed to a list, but I haven't found one example of it being used for conditional rendering. Is it achievable? Maybe there's another addon that does this?
I've seen this same problem posted many times. In short: you need to conditionally render the children inside of <ReactCSSTransitionGroup>, not <ReactCSSTransitionGroup> itself. <ReactCSSTransitionGroup> needs to mount once and then stay, it's the children that get added and removed.
content.js
class Content extends Component {
render() {
const transitionOptions = {
transitionName: "fade",
transitionEnterTimeout: 500,
transitionLeaveTimeout: 500
}
let theChild = undefined;
if (this.props.page === 'one') {
theChild = <Comp1 key="comp1" />;
} else {
theChild = <Comp2 key="comp2" />;
}
return (
<div>
<ReactCSSTransitionGroup {...transitionOptions}>
{theChild}
</ReactCSSTransitionGroup>
</div>
);
}
}
Note that you should also add a unique key prop to each child inside of a <ReactCSSTransitionGroup>. That helps the component identify which children are unique in order to properly animate them in and out.
Here is a snippet from my code
render() {
return (
<CSSTransitionGroup
transitionName="slide"
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
{ id === targetID ? (
<div>
<SectionList id={id} />
</div>
) : '' }
</CSSTransitionGroup>
)
}

TransitionGroup and CssTransition: Exit transition not applied

I migrated from the old CSSTransitionGroup to the new react-transition-group CSSTransition and TransitionGroup.
I'm creating (hacking) an overlay loader and I'm trying to apply animation to the overlay when it appears and disappears.
Specifically when I pass an active=true props to the LoadingOverlayWrapper then a new CSSTransition is added to the TransitionGroup (the Fadecomponent) that wraps the overlay to show.
When active=false then the CSSTransition is removed from within the TransitionGroup (the direct child of TransitionGroupis null).
This is the relevant part of the code:
import React, {Children} from 'react'
import PropTypes from 'prop-types'
import {CSSTransition, TransitionGroup} from 'react-transition-group'
import LoadingOverlay from "./LoadingOverlay";
import styles from './Overlay.sass';
const FirstChild = props => Children.toArray(props.children)[0] || null;
const Fade = (props) => (
<CSSTransition
{...props}
timeout={500}
classNames={{
appear: styles.appear,
appearActive: styles.appearActive,
enter: styles.enter,
enterActive: styles.enterActive,
exit: styles.exit,
exitActive: styles.exitActive
}}
>
<FirstChild {...props} />
</CSSTransition>
);
class LoadingOverlayWrapper extends React.Component {
render() {
const {active} = this.props;
return (
<div>
<TransitionGroup>
{
active ?
(
<Fade key='transition_effect'>
<LoadingOverlay key='the_dimmer' {...this.props} />
</Fade>
)
:
null
}
</TransitionGroup>
{this.props.children}
</div>
)
}
}
And this is the relevant sass file (imported as css module):
.enter, .appear
opacity: 0.01
.appearActive, .enterActive
opacity: 1
transition: opacity .5s ease-in
.exit, .leave
opacity: 0.01
.exitActive, .leaveActive
opacity: 0
transition: opacity .5s ease-in
The enter (or appear, not sure here) transition works.
The problem is that when I remove the Fade component, being replace by null then the exit transition is not applied (or not visible) but I get no error, everything else works as intended.
I'm not sure how to debug or proceed here given I have little experience with React TransitionGroup.
I've been struggling with the same issue - the solution that worked for me is using the childFactory prop on the <TransitionGroup> like so:
<TransitionGroup
childFactory={child => React.cloneElement(child)}
>
{
active ?
(
<Fade key='transition_effect'>
<LoadingOverlay key='the_dimmer' {...this.props} />
</Fade>
)
:
null
}
</TransitionGroup>
import { CSSTransition } from 'react-transition-group';
<CSSTransition
in={toShow} // boolean value passed via state/props to either mount or unmount this component
timeout={300}
classNames='my-element' // IMP!
unmountOnExit
>
<ComponentToBeAnimated />
</CSSTransition>
NOTE: Make sure to apply below styles using the class property in CSS:
.my-element-enter {
opacity: 0;
transform: scale(0.9);
}
.my-element-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.my-element-exit {
opacity: 1;
}
.my-element-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}

Categories