4

I'm wondering if ReactCSSTransitionGroup is right for my use case or not.

On one route I have a search button, when the user clicks on it the button should transition away while it loads in the search page. Once loaded, some area of the page will animate in from the side.

I'm using this with React Router so entire page transitions seem simple but animating specific elements in this way seems more challenging.

Should ReactCSSTransitionGroup be able to handle this or should I look into alternatives?

fetimo
  • 235
  • 5
  • 13

2 Answers2

3

I think the answer is "Yes, it's possible".

I wrote a proof of concept on CodePen. There are a few gotchas with ReactCSSTransitionGroup, one of which is mentioned in this discussion on Google Groups. There, someone says:

You need to keep the ReactCSSTransitionGroup around and add/remove its contents.

So, basically, if you have a component that will animate its children, you need to wrap the children in ReactCSSTransitionGroup. Here's some pseudocode based on my pen:

Let's say your router setup looks like this:

<Router history={hashHistory}>
  <Route path="/" component={App}>
    <IndexRoute component={Home}/>
    <Route path="/repos" component={Repos}>
      <Route path="/repos/:userName/:repoName" component={Repo}/>
    </Route>
    <Route path="/about" component={About}/>
  </Route>
</Router>

I want to animate the child components of <App>. Here's what it might look like:

function App(props) {
  return (
    <div>
      <h1>App</h1>
      <nav>
        <ul>
          <li><NavLink to="/" onlyActiveOnIndex={true}>Home</NavLink></li>
          <li><NavLink to="/about">About</NavLink></li>
          <li><NavLink to="/repos">Repos</NavLink></li>
        </ul>
      </nav>
      <ReactCSSTransitionGroup>
        {React.cloneElement(props.children, {
          key: getKey(props.location.pathname)
        })}
      </ReactCSSTransitionGroup>
    </div>
  );
}

Note that you need to have ReactCSSTransitionGroup outside the child components. Also, each element needs a unique key so that ReactCSSTransitionGroup can keep track of them. Pathname is generally a good candidate. In my particular use case, I used a function to get a certain prefix of the pathname, so that the transition at this level doesn't happen if I navigate between repos. You'll see what I mean.

I can do something similar in the <Repos> component:

function Repos(props) {
  return (
    <div>
      <h2>Repos</h2>
      <ul>
        <li><NavLink to="/repos/reactjs/react-router">React Router</NavLink></li>
        <li><NavLink to="/repos/facebook/react">React</NavLink></li>
      </ul>
      <ReactCSSTransitionGroup>
        {props.children ?
          React.cloneElement(props.children, {
            key: props.location.pathname
          }) :
          null}
      </ReactCSSTransitionGroup>
    </div>
  );
}

Similar idea here. This time I used the full pathname because, at this level, I do want a transition between individual repos.

I've yet to try this on a large app, so I'll defer to someone with more experience. However, as I said earlier, it should at least be possible. You should be able to compose simple animations to get complex animations.

Frank Tan
  • 4,234
  • 2
  • 19
  • 29
0

ReactCSSTransitionGroup is actually a high-level interface to ReactTransitionGroup. If you wrap your child elements with <TransitionGroup />, you will receive additional lifecycle methods like componentWillEnter(callback), componentWillLeave(callback), etc... You can run animations in these methods, and then fire the callback when the animation is done. I use jQuery below for the animation, but you can use any animation library to control the elements.

Code that illustrates this concept:

<Router history={hashHistory}>
  <Route path="/" component={Parent}>
    <Route path="/child" component={Child}/>
  </Route>
</Router>


var Child = React.createClass({
  componentWillEnter: function(callback) {
    var $this = $(ReactDOM.findDOMNode(this));
    $this.css({ opacity: 0 });
    $this.fadeIn('slow', callback);
  },
  componentWillLeave: function(callback) {
    var $this = $(ReactDOM.findDOMNode(this));
    $this.fadeOut('slow', callback);
  },
  render: function() {
    return (
      <p>This child element will fade in when entering and fade out when leaving.</p>
    )
  }
});

var Parent = React.createClass({
  render: function() {
    return (
      <h1>Parent element</h1>
      <TransitionGroup>
        {this.props.children}
      </TransitionGroup>
    )
  }
});