10

I've been using classes to control open/close behaviors w/ a CSS transition for effect. I've used this on other components, no problem, but for some reason the same method is failing me in this scenario...

The open/close behaviors attach (I see the end difference w/ background color and translateY) but the CSS transition itself is lost... any ideas why I lose my CSS transition but everything else is working as expected?

Note, when I manually toggle the open/closed classes using Developer Tools, it works just fine! The CSS transition picks up!

So what's up with the React on click to toggle a class applying, but losing the CSS transition?

class Projects extends React.Component {
    /* constructor, etc... */
    render() {
        return (
            <div className="projects-nav-container">
                <div className="center title monospace" onClick={this.props._toggleProjectNav} id="Menu">Menu</div>
                <ul className={`projects-nav ${this.props._isProjectNavOpen ? 'open' : 'closed'}`}>
                    { PROJECTS.map((project, index) => 
                    <li key={index} >
                         <p>project here</p>
                    </li>
                    ) }
                </ul>
            </div>
        );
    }
}

App.js looks as such:

class App extends React.Component {
    constructor() {
        super();
        this.state = {
            _isProjectNavOpen: true
        }
        this._toggleProjectNav = this._toggleProjectNav.bind(this);
    }
    _toggleProjectNav() {
        this.setState(prevState => ({
            _isProjectNavOpen: !prevState._isProjectNavOpen,
        }));
    }
    render() {
        <div>
            <Router>
                <Route path="/projects" component={(props, state, params) => 
                    <Projects 
                        _toggleProjectNav={this._toggleProjectNav}
                        _isProjectNavOpen={this.state._isProjectNavOpen} 
                    {...props} />} />
            </Router>
        </div>
    }
}

SCSS:

.projects-nav {
    @include transition(all $transition_speed ease);
    &.open {
        @include transform(translateY(0));
        background: red
    }
    &.closed {
        @include transform(translateY(-100vh));
        background: green;
    }
}
vesperae
  • 1,291
  • 2
  • 19
  • 28
  • 1
    It must be replacing the DOM element entirely. When you have the DevTools open, and you toggle the menu, does the whole DOM element blink, or just its `class` attribute? – Dan Aug 23 '18 at 16:03
  • My bet is on the Router library. Can you please try if it works, if you don't wrap the `Projects` with the `Route` and `Router`? Also which react-router version do you use? – Jordan Enev Aug 24 '18 at 10:18
  • Your code doesn't show an import of CSS transition group ? import { CSSTransitionGroup } from 'react-transition-group' .... There's a few issues with transitions and react (which is why the original add-on was released presumably).. there's a good article [here](https://medium.com/@joethedave/achieving-ui-animations-with-react-the-right-way-562fa8a91935) – Rachel Gallen Aug 25 '18 at 14:36

3 Answers3

12

It is because of react-router think of each route as a case in the switch statement, and the path in the <Route /> component being a key for that case. When the path gets changed the component is unmounted completely. Hence you don't see the CSS transitions because the DOM for it doesn't exist anymore.

If you want to animate with react-router. You need to use a react utility library called react-transition-group. Here is a detailed example by the author of react-router which you can follow. React Router Animation Example

I hope this helps.

Also there is this great talk on youtube for about 30 minutes that talks about how to do really nice animations in react with routing https://www.youtube.com/watch?v=S3u-ccn4PEM Cheers :)

Adeel Imran
  • 13,166
  • 8
  • 62
  • 77
4

Indeed, the problem is that react-router is unmounting your component and mounting it again with the new classes, losing the CSS transition in the process. To solve this issue, simply use render instead of component on the <Route> component.

As to why this works, from react-router documentation:

Instead of having a new React element created for you using the component prop, you can pass in a function to be called when the location matches. The render prop receives all the same route props as the component render prop.

For a more detailed explanation, you could read the question react router difference between component and render.

In summary, App.js should look like this:

class App extends React.Component {
    constructor() {
        super();
        this.state = {
            _isProjectNavOpen: true
        }
        this._toggleProjectNav = this._toggleProjectNav.bind(this);
    }
    _toggleProjectNav() {
        this.setState(prevState => ({
            _isProjectNavOpen: !prevState._isProjectNavOpen,
        }));
    }
    render() {
        <div>
            <Router>
                <Route path="/projects" render={(props, state, params) => 
                    <Projects 
                        _toggleProjectNav={this._toggleProjectNav}
                        _isProjectNavOpen={this.state._isProjectNavOpen} 
                    {...props} />} />
            </Router>
        </div>
    }
}

I created a CodeSandbox using render and it seems to work properly!

Cheers!

Thiago Murakami
  • 965
  • 7
  • 11
3

Change key have to update element.

Try this code:

class App extends React.Component {
    constructor() {
        super();
        this.state = {
            _isProjectNavOpen: true,
            _ProjectsKey: 1,
            _RouteKey: 1
        }
        this._toggleProjectNav = this._toggleProjectNav.bind(this);
    }
    _toggleProjectNav() {
        this.setState(prevState => ({
            _isProjectNavOpen: !prevState._isProjectNavOpen,
            _ProjectsKey: prevState._ProjectsKey + 1,
            _RouteKey: prevState._RouteKey + 1
        }));
    }
    render() {
        <div>
            <Router>
                <Route key={this.state._RouteKey} path="/projects" component={(props, state, params) => 
                    <Projects 
                        _toggleProjectNav={this._toggleProjectNav}
                        _isProjectNavOpen={this.state._isProjectNavOpen} 
                        key={this.state._ProjectsKey} 
                    {...props} />} />
            </Router>
        </div>
    }
}
morteza ataiy
  • 541
  • 4
  • 12