cheslijones
cheslijones

Reputation: 9194

CSS transitions in React not working when state becomes true

I have an error bar that needs to ease-in when error = true.

In the render() I have:

render() {
    const { errors } = this.props;

    return (
        <div id='messages'>
                <div className={ errors ? 'show-messages' : 'hidden-messages' }>
                    <Container>{ this.renderMessage() }</Container>
                </div>
        </div>
    )
}

The user tries to login, for example, and if the credentials are bad the server responds which causes this.props.errors to become true and show-messages.

However, I want the bar to ease-in.

This is the latest CSS I have tried:

.hidden-messages {
    visibility: hidden;
    transition: width 2s, height 2s;
    -webkit-transition: width 2s, height 4s;
    transition-delay: 5s;
}

.show-messages {
    visibility: visible;
    background-color: #FFF;
    transition: width 2s, height 2s;
    -webkit-transition: width 2s, height 4s;
    transition-delay: 5s;
}

Started with just transition:, but was't getting anywhere. Read somewhere you need to add transition-delay so tried that and it didn't work.

I think what the issue the ternary is basically an on/off switch and doesn't really establish any kind of relationship between .hidden-messages and .show-messages or something. In other words, ease in from what... as far as it knows it has been visible? How do I accomplish this?


EDIT: I tried what Jason McFarlane provided in his CodePen example, and variations of it, and couldn't reproduce the results.

Also, based on the below statement I did modify a few things:

If the state you want is to hide/show then you should toggle a hide or show class on top of the default state.

Example: if your default state is to have the messages shown you always want that class to be on that div.

Since my default state is hide-messages, I ended up with the below. Also, I did change the ternary in his example from show-messages.hide-messages to show-messages hide-messages`. I looked at how the DOM was being rendered in the CodePen example, and the latter is how it was changing the class, so I changed to that. Still no luck.

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearMessages } from '../../actions/messages';

import {
    Alert,
    Container
} from 'reactstrap';

import styles from '../../../assets/scss/messages.scss';

class Messages extends Component {
    constructor(props) {
        super(props);
        this.state = {
        };
        this.closeMessage = this.closeMessage.bind(this);
    }   
    
    closeMessage() {
        this.props.clearMessages();
    }

    renderMessage() {
        const { errors } = this.props;

        if (errors) {
            return (
                <Alert color='danger'>
                    <i onClick={ this.closeMessage } className='float-right fas fa-times'></i>
                    <span 
                        dangerouslySetInnerHTML={{__html: errors.non_field_errors[0]}}>
                    </span>
                </Alert>
            );
        }
    }

    render() {
        const { errors } = this.props;

        return (
            <div id='messages'>
                <div className={ errors ? 'hide-messages show-messages' : 'hide-messages' }>
                    <Container>{ this.renderMessage() }</Container>
                </div>
            </div>
        )
    }
}

function mapStateToProps(state) {
    return {
        errors: state.messages.errors 
    }
}

export default connect(mapStateToProps, { clearMessages })(Messages);

@import 'declarations';

#messages {
    position: relative; 
    top: 105px;

    .hide-messages {
        visibility: hidden;
        height: 0;
        width: 0;
    }

    .hide-messages.show-messages {
        visibility: visible;
        background-color: $red;
        transition: height 5s ease-in !important;
        height: 100%;
        width: 100%;
    }

    .alert-danger {
        background-color: $red;
        margin-bottom: 0;
        border: none;
        padding-left: 0;
        padding-right: 0;
        color: white;

        i {
            cursor: pointer;
        }
    }
}

This is what it loos like. So the default is no red banner and it should ease in.

enter image description here

Upvotes: 2

Views: 7745

Answers (1)

Jason McFarlane
Jason McFarlane

Reputation: 2175

The ternary is correct as the on off switch but the implementation is where you are facing the issue. On a CSS transition you want a default state and to toggle an additional state on or off of that.

If the state you want is to hide/show then you should toggle a hide or show class on top of the default state.

Example: if your default state is to have the messages shown you always want that class to be on that div.

<div className={ errors ? 'show-messages' : 'show-messages.hide-messages' }>

You are then going to have to change your CSS

.show-messages {
  visibility: visible;
  transition: height 0.3s ease-in;
  height:200px;
  width: 200px;
  background: #d8d8d8;
}

.show-messages.hide-messages {
  visibility: hidden;
  height: 0;
  width: 0;
}

I created a codepen https://codepen.io/anon/pen/gKmpgw you will have to play around with the CSS attributes you want to animate

Upvotes: 5

Related Questions