6

UPDATE:

For those facing the same problem: I found a similar issue posted on the git page of ReactTransitionGroup with the solution in there: https://github.com/reactjs/react-transition-group/issues/182.

To be honest, 90% of the solution blew right over my head and I don't understand much of it at all, this is the only part that made sense to me(taken from React TransitionGroup and React.cloneElement do not send updated props which is linked the git issue):

Passing Props to Leaving Children? Now the question is, why aren't updated props being passed to the leaving element? Well, how would it receive props? Props are passed from a parent component to a child component. If you look at the example JSX above, you can see that the leaving element is in a detached state. It has no parent and it is only rendered because the is storing it in its state.

I have to further research and understand the way parent and children components communicate. I'm still new to web development but things are slowly starting to make sense.

------------------

Initial question asked:

When you are attempting to inject the state to the children of your through React.cloneElement, the leaving component is not one of those children.

I have a small app that contains a nav menu and routes nested between a switch container. Every click on a nav item, I check if the index is higher or lower to set the animation state (Left or right) accordingly and then store the new index of the clicked nav item.

Everytime I switch routes, the animation applied is either fade-left or fade-right. Now this all works fine, but the only problem is when the class switches to right from left the EXIT animation applied uses the previous class given.

I understand why this is happening, It's because the exit class used is the one initially set, and when the new class is applied only on the second change does the new exit take affect. The transition documentation is very limited and I couldn't find anything.

const { Component } = React;
const { TransitionGroup, CSSTransition } = ReactTransitionGroup;
const { HashRouter, Route, NavLink, Switch, withRouter } = ReactRouterDOM;

var icons = [
 {icon: 'far fa-address-book active', path: '/'},
 {icon: 'fab fa-linkedin', path: '/linked'},
 {icon: 'fas fa-gamepad', path: '/hobbies'}
];

const Nav = props => {
  return (
   <div className="nav">
    <ul>
      { 
        icons.map((icon, index) => {
          return (
            <li
              key={index}
              onClick={() => props.clicked(index)}
              className={props.active == index ? 'active' : false}>
                <NavLink to={icon.path} className="NavLink">
                  <i className={icon.icon} key={icon.icon}></i>
                </NavLink>
            </li>
          );
        })
      }
    </ul>
  </div>
);
}

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentViewIndex: 0,
      animationDirection: 'right'
    }

   this.setIndex = this.setIndex.bind(this);
 }

 setIndex(index){
   const animationDirection = index < this.state.currentViewIndex ? 'left' : 'right';
   this.setState({currentViewIndex: index, animationDirection});
 }

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

    return (
      <div className="container">
        <Nav active={this.state.currentViewIndex} clicked={() => this.setIndex()} />
        <TransitionGroup>
          <CSSTransition
            key={location.pathname}
            classNames={`fade-${this.state.animationDirection}`}
            timeout={1000}>
            <Switch location={location}>
              <Route exact path="/" render={() => <h1>Home</h2>} />
              <Route path="/linked" render={() => <h1>Linked</h2>} />
              <Route path="/hobbies" render={() => <h1>Hobbies</h2>} />
            </Switch>
          </CSSTransition>
        </TransitionGroup>
      </div>
    );
   }
 }

const AppWithRouter = withRouter(App);

ReactDOM.render(<HashRouter><AppWithRouter /></HashRouter>, document.querySelector('#root'));

The CSS classes applied to transitions:

.fade-left-enter {
  position: unset;
  transform: translateX(-320px);
}

.fade-left-enter.fade-left-enter-active {
  position: unset;
  transform: translateX(0);
  transition: transform .5s ease-in;
}

.fade-left-exit {
  position: absolute;
  bottom: 0;
  transform: translateX(0);
}

.fade-left-exit.fade-left-exit-active {
  transform: translateX(320px);
  transition: transform .5s ease-in;
}

.fade-right-enter {
  position: unset;
  transform: translateX(320px);
} 

.fade-right-enter.fade-right-enter-active {
  position: unset;
  transform: translateX(0);
  transition: transform .5s ease-in;
}

.fade-right-exit {
  position: absolute;
  bottom: 0;
  transform: translateX(0);
}

.fade-right-exit.fade-right-exit-active {
  transform: translateX(-320px);
  transition: transform .5s ease-in;
}
Raydot
  • 1,458
  • 1
  • 23
  • 38
Marwan Fikrat
  • 137
  • 1
  • 8

1 Answers1

8

The reason for your problem is that the exiting component is already detached and therefor does not get any updates. You can find a very good explanation of your problem here.

You can use the prop childFactory from <TransitionGroup> to solve this:

childFactory

You may need to apply reactive updates to a child as it is exiting. This is generally done by using cloneElement however in the case of an exiting child the element has already been removed and not accessible to the consumer.

If you do need to update a child as it leaves you can provide a childFactory to wrap every child, even the ones that are leaving.

Try the following code changes in your render method:

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

  const classNames = `fade-${this.state.animationDirection}`; // <- change here

  return (
    <div className="container">
      <Nav active={this.state.currentViewIndex} clicked={() => this.setIndex()} />
      <TransitionGroup 
        childFactory={child => React.cloneElement(child, { classNames })} // <- change here
      >
        <CSSTransition
          key={location.pathname}
          classNames={classNames} // <- change here
          timeout={1000}
        >
            <Switch location={location}>
              <Route exact path="/" render={() => <h1>Home</h2>} />
              <Route path="/linked" render={() => <h1>Linked</h2>} />
              <Route path="/hobbies" render={() => <h1>Hobbies</h2>} />
            </Switch>
        </CSSTransition>
      </TransitionGroup>
    </div>
  );

}

iSteffi
  • 178
  • 2
  • 11