hzdg / gsap-react-plugin

A GSAP plugin for tweening React.js component state.
Other
130 stars 8 forks source link

State changes that don't happen through tweens can break them #3

Open lettertwo opened 10 years ago

lettertwo commented 10 years ago

Because state changes in React components can be asynchronous, the plugin has to track a proxy for the component state so that multiple tweens can act in between component updates.

But, because the component doesn't know about this proxy, there is no way for the tweens to know when the state is changed outside of the tweening process. For example:

React.createClass({
  getInitialState: function() {
    return {x: 0}
  },
  componentDidMount: function() {
    // Tween state.x from the initial value of 0 to 100.
    // When that tween completes, set the state back to 0.
    TweenLite
      .to(this, 0.5, {state: {x: 100}})
      .addCallback('onComplete', function(){
        this.setState({x: 0});
      }.bind(this));
  },
  componentDidUpdate: function() {
    // You would expect this to tween to 100 starting from 0, since the state
    // was updated in the 'onComplete' callback in `componentDidMount`, but it
    // doesn't because the internal plugin proxy doesn't know the state changed!
    TweenLite
      .to(this, 0.5, {state: {x: 100}});
  },
  render: function() { /* etc... */ }
});

There doesn't seem to be any good options for solving this.

Here are some bad ones:

  1. mutate target.state directly (instead of maintaining an separate tween state and binding them together)
    • bad because it will cause issues for componentShouldUpdate and componentDidUpdate, when comparisons between previous and next tween states are inaccurate (since the state gets mutated directly between renders by the tween)
  2. override target.setState to make sure it also updates the tween state
    • this just seems evil
  3. add a setTweenState method to the component that can update the tween proxy object and call setState
    • this means your component has to use this special method everywhere that it would normally use setState (or at least everywhere that the component would want to update state that is also being tweened)
    • might be the best option?
lettertwo commented 10 years ago

Maybe we can circumvent the issue by inspecting the tween instance when the plugin initializes, and look for the immediateRender flag. If it's present, we pull the start values from component.state instead of tweenState.

lettertwo commented 10 years ago

Just published v1.0.2 which might be a workable solution to this problem. I guess we'll have to wait and see...