1

I have a React app that is using the CSSTransition component from react-transition-group: when the component appears, everything behaves as expected (a 0.5s transition from opacity: 0 to opacity: 1), however when the component exits, the transition is not applied and it just immediately disappears. Can anybody help me figure out why?

Render method in component:

    render(){

        const countries = geoUrl.objects.ne_50m_admin_0_countries.geometries;
        const { handleEnter, handleList, list } = this.props;

        return (
            <CSSTransition
                classNames="transition"
                transitionAppearTimeout={50000}
                timeout={500000}
                key={ list }
                in={ list } // this is a boolean value passed from the parent component, it is initially set to false but changes to true when this component is rendered
                unmountOnExit
                appear
            >   
                <div className="overlay" id="list">      
                    <div className="wrapper" ref={this.setWrapperRef}>
                        <aside className="list">
                            <a className="close" href="#home" onClick={handleList}>&times;</a>
                            <ul className="countryList">
                                { countries.sort((a, b) => (a.properties.NAME > b.properties.NAME) ? 1 : -1).map(geo =>
                                    geo.properties.COUNTRY ?
                                    <li className="listItem" key={ `${geo.properties.ISO_A3}${geo.properties.name}` }><a href="#country" onClick={() => {
                                        const { NAME, DISH, DESCRIPTION, PHOTO, RECIPE } = geo.properties;
                                        handleEnter(NAME, DISH, DESCRIPTION, PHOTO, RECIPE);
                                    }}>{ geo.properties.NAME }</a></li>
                                    : null
                                )}
                            </ul>
                        </aside>
                    </div>
                </div>
            </CSSTransition>
        );
    }

CSS:

.transition-appear {
    opacity: 0.01;
  }
  
.transition-appear.transition-appear-active {
    opacity: 1;
    transition: opacity .5s ease-in;
}

.transition-enter {
    opacity: 0;
}

.transition-enter-active {
    opacity: 1;
    transition: opacity .5s ease-in;
}

.transition-exit {
    opacity: 1;
  }
  
.transition-exit-active {
    opacity: 0;
    transition: opacity .5s ease-in;
}

1 Answers1

2

I also struggled with the transition out, but have come up with a solution. I can't tell what your final result should look like in the UI, whether you want to transition in between conditionally rendered elements on the page, or just a transition when the component mounts and unmounts, but I think you want to do the former.

To transition between two or more elements on the same page, you will need to wrap your <CSSTransition> tags in <TransitionGroup> tags (and import it alongside CSSTransition). When you do this, you will need to provide a unique key property to the <CSSTransition> tag, it can't just be a boolean like it looks like you have. You also will need to slightly modify your CSS.

.transition-enter {
  opacity: 0;
}

.transition-enter.transition-enter-active {
  opacity: 1;
  transition: opacity 0.5s ease-in;
}

.transition-enter-done {
  opacity: 1;
}

.transition-exit {
  opacity: 1;
}

.transition-exit.transition-exit-active {
  opacity: 0;
  transition: opacity 0.5s ease-in;
}

.transition-exit-done {
  opacity: 0;
}

And the Transition tags:

import {CSSTransition, TransitionGroup} from "react-transition-group"

//...the rest of your code

return (
    <TransitionGroup>
       <CSSTransition
          key={foo} //something unique to the element being transitioned
          classNames="transition"
          timeout={500}
       >
          <div className="overlay" id="list">      
                    <div className="wrapper" ref={this.setWrapperRef}>
                        <aside className="list">
                            <a className="close" href="#home" onClick={handleList}>&times;</a>
                            <ul className="countryList">
                                { countries.sort((a, b) => (a.properties.NAME > b.properties.NAME) ? 1 : -1).map(geo =>
                                    geo.properties.COUNTRY ?
                                    <li className="listItem" key={ `${geo.properties.ISO_A3}${geo.properties.name}` }><a href="#country" onClick={() => {
                                        const { NAME, DISH, DESCRIPTION, PHOTO, RECIPE } = geo.properties;
                                        handleEnter(NAME, DISH, DESCRIPTION, PHOTO, RECIPE);
                                    }}>{ geo.properties.NAME }</a></li>
                                    : null
                                )}
                            </ul>
                        </aside>
                    </div>
                </div>
       </CSSTransition>
    </TransitionGroup>

)

Please note: You do not need appear, in, or unmountOnExit properties for this method, but you will have issues with duplicate elements appearing in the DOM during the transition, becuase react-transition-group actually clones the element and deletes the old one (which is why it needs a unique key). The only way to achieve a cross-fade transition is to take your elements out of the document flow with position absolute, so they overlap as one transitions in, and the other out.

I can't test your exact code, because there is not enough information, but I put together a very basic codesandbox that demonstrates the method with 2 conditionally rendered elements:

https://codesandbox.io/s/long-sea-9rtw9?file=/src/Test.js

PsiKai
  • 1,803
  • 1
  • 5
  • 19