Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smoothly Transitioning Between ReactJS Prop Changes

Tags:

reactjs

There are two buttons that toggle the layout of an item. When either button is clicked, I'd like the item to fade out, change, then fade back in.

Using ReactJS, I'm running into two problems:

  1. Using componentDidUpdate to trigger the "fade back in" event causes a loop; changing the state re-triggers componentDidUpdate endlessly.

  2. Using componentWillReceiveProps allows me to update the class on the element to have it fade out, but it also immediately changes the layout. I need to delay the change until it's invisible.

Thoughts? Have I constructed this wrong?

(The code is below, but something is broken in Stack's version that works in JSFiddle: https://jsfiddle.net/nathanziarek/69z2wepo/15009/)

var Hello = React.createClass({
    getInitialState: function() {
    	return { visible: " x-hidden" };
    },
    render: function() {
        return <div className={"hello" + this.state.visible}>Hello {this.props.name}</div>;
    },
    componentDidMount: function(){
    	this.setState({ visible: "" })
    },
    componentWillReceiveProps: function(){
    	// The item is changing, make it invisible
        // Wait until it's done being invisible before changing
        // anything?
    	this.setState({ visible: " x-hidden" })
    },
    componentDidUpdate: function(){
    	// The item has changed, make it visible
        // Setting anything here causes this function
        // to get called again, creating a loop
    	// this.setState({ visible: "" })
    }
});

var Button = React.createClass({
    render: function() {
        return <button onClick={this.handleClick}>{this.props.label}</button>;
    },
    handleClick: function() {
    	React.render(
        	<span>
            	<Button label="Universe"/>
                <Button label="World"/>
                <Hello name={this.props.label} />
            </span>, 
            document.getElementById('container')
        );
    }
});
 
React.render(
    <span>
        <Button label="Universe"/>
        <Button label="World"/>
        <Hello name="_______" />
    </span>, 
    document.getElementById('container')
);
.hello {
    opacity: 1;
    transition: 300ms opacity linear;
    -webkit-transition: 300ms opacity linear;
}

.x-hidden { opacity: 0 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/JSXTransformer.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/react-with-addons.js"></script>

<script src="https://facebook.github.io/react/js/jsfiddle-integration.js"></script>

<div id="container">
  <!-- This element's contents will be replaced with your component. -->
</div>
like image 604
nathanziarek Avatar asked Oct 20 '22 03:10

nathanziarek


2 Answers

I would look into using React's CSSTransitionGroup add-on component. You'll have to do some restructuring, but this will give you the behavior you want without having to add a bunch of logic around setting CSS classes; React will take care of animating (in your case, fading) components that are entering/leaving the DOM.

Unrelated: I would also avoid re-rendering your entire component like how you're doing it in your button click event. This breaks the flow of React and will inevitably cause problems. Prefer changing state and pushing down new props.

like image 79
Jim Skerritt Avatar answered Oct 22 '22 21:10

Jim Skerritt


Easiest way to do this is to have both on screen then to do the fade entirely with css, and essentially completely omit React from the process other than to place the appropriate classes.

If you're fading between them you need both sets of contents anyway. That, in turn, means there's no reason to not have these in the document, and that in turn means it's just CSS hide/show tomfoolery, and there's no need for any kind of React lifecycle stuff.

You're overcomplicating it by trying to have React handle way too much.

Supporting CSS:

#foo span { 
  opacity: 0; 
  position: absolute; 
  top: 0; left: 0; bottom: 0; right: 0; 
  transition: all 0.5s ease; 
}

#foo span.show { 
  opacity: 1; 
}

Then the relevant React:

var Foo = react.createComponent({ 

  render: function() { 

    var showKey = this.props.showKey,
        Things = {
          a: 'Apple',
          b: 'Bear',
          c: 'Cold inevitable doom'
        };

    return (
      <div id="foo">
        { Things.map(X,k) { 
          return <span class={(showKey === k)? 'show':undefined}>{X}</span> 
        }}
      </div>
    );

  }

});

You'll find that as you switch that control's showKey, it will fade in and out its relevant contents as appropriate.

like image 29
John Haugeland Avatar answered Oct 22 '22 23:10

John Haugeland