Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
KevinK

How to animate async data? React / Redux

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Hello,

 

I'm having trouble trying to debug what is going wrong with my animations. I was able to use mock data and get the desired effect of having my animation stagger through my array of card objects. However, once I started using real data my animations will no longer trigger. Now there are a few strange observations I made:

1. My data is making it into the gsap related mapping func (I have a console log that is proving the cards do exist inside the map)

2. The callback at the very end of my animation is still firing and updating state.

 

So, in short, despite it working with mock data when I switched over to 'live-data' it no longer even triggers the animation despite the `console.log` showing that there is indeed data to map over and the fact that the callback at the end of the animation still triggers...

 

Any ideas?

 

Here is the component:

import React, { Component } from "react";
import { connect } from "react-redux";
import "./PlayerHand.scss";
import TreasureCard from "../TreasureCard/TreasureCard";
import ActionCard from "../ActionCard/ActionCard";
import Card from "../Card/Card";
import { TimelineMax } from "gsap";
import { gsapCardsCycleIn } from "../../greenSock/animations";
import { mockPlayerData } from "../../mockData";

export class PlayerHand extends Component {
  constructor(props) {
    super(props);
    this.cards = [];
    this.tl = new TimelineMax({ paused: true });
    this.state = { renderHand: false };
  }

  componentDidMount = () => {
    gsapCardsCycleIn(this.tl, this.cards, this.showUserHand);
  };

  // componentDidUpdate(prevProps, prevState) {
  //   if (prevProps.playerHand !== this.props.playerHand) this.tl.restart();
  // }

  showUserHand = () => {
    this.setState({ renderHand: true });
  };

  render() {
    // this.tl
    //   .kill()
    //   .clear()
    //   .pause(0);
    return (
      <React.Fragment>
        <section className='hand-animations'>
          {this.props.playerHand &&
            this.props.playerHand.map((card, i) => {
      // this console.log is logging the relevant data, but for some reason nothing is animating
              console.log(card);
              return (
                <div className='animated-card' key={i} ref={div => (this.cards[i] = div)}>
                  <p>{card.name}</p>
                </div>
              );
            })}
        </section>
        <button onClick={() => this.tl.play()}>Start</button>
        <button onClick={() => this.tl.reverse()}>Reverse</button>
        <button onClick={() => this.tl.restart()}>Restart</button>

        <section
          className='PlayerHand'
          style={
            this.state.renderHand
              ? { transform: "translateY(50%)" }
              : { transform: "translateY(100%)" }
          }
        >
          {this.state.renderHand &&
            this.props.playerHand.map(card => {
              if (card.category[0] === "Money") {
                return <TreasureCard card={card} key={card.id} />;
              }
              if (card.category[0] === "Action") {
                return <ActionCard card={card} key={card.id} />;
              }
              if (card.category[0] === "Victory") {
                return (
                  <Card
                    name={card.name}
                    desc={card.desc}
                    category={card.category}
                    image={card.image}
                    cost={card.cost}
                    id={card.id}
                    key={card.id}
                  />
                );
              }
            })}
        </section>
      </React.Fragment>
    );
  }
}

export const mapStateToProps = state => ({
  playerHand: state.playerHand
});

export default connect(mapStateToProps)(PlayerHand);

Here is the GSAP animation I built and imported

import { Back } from "gsap";
export const gsapCardsCycleIn = (timeline, dataArray, onComplete) => {
  timeline
    .to(
      dataArray,
      0.8,
      {
        scale: 1.09,
        opacity: 1,
        ease: Back.easeInOut.config(10)
      },
      0.3
    )
    .staggerTo(
      dataArray,
      0.4,
      {
        left: "45vw",
        bottom: "25vh",
        scale: 1.4,
        cycle: {
          rotation: i => Math.random() * ((i + 3) * 2),
          ease: i => Back.easeOut.config((i + 1) * 0.5)
        }
      },
      1.2
    )
    .staggerTo(
      dataArray,
      0.5,
      {
        left: "45vw",
        bottom: "5vh",
        rotation: 0,
        scale: 1
      },
      0.2
    )
    .to(dataArray, 0.7, {
      opacity: 0,
      y: "100vh",
      ease: Back.easeInOut.config(1.2),

      delay: 0.4
    })
    .to(dataArray, 0, { onComplete: onComplete, delay: 1 });
};

 

Link to comment
Share on other sites

Hi and welcome to the GreenSock forums.

 

The main issue here could be the fact that you're creating the animation when the component is mounted, but since you're working with async data, the componentDidMount event is triggered after the first render, but then the server response comes back and a new render is triggered with the updated data, but the GSAP instance is already created with other DOM elements (the ones from the first render) so that's why after the re-render generated from the server response, the actual DOM elements in the screen are not the same that GSAP has in the Timeline instance.

 

You're pretty close to a solution though, so it won't be too hard to implement I think. Don't use the componentDidMount event to create the animation and use the componentDidUpdate to do so, keeping in mind that if your data will be updated at some point is better to stop, kill and clear the timeline after a new server response has arrived.

 

Here is an extremely simplified version using axios to fetch some data from a dummy API:

 

https://codesandbox.io/s/gsap-animation-react-async-data-c0017

 

Hopefully is enough to help.

Happy Tweening!!!

  • Like 4
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×