Jump to content
Search Community

TweenMax.set in React componentDidMount

egorrr test
Moderator Tag

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

Hi, I am pretty new in GSAP and don't have a lot of exp. in react, so I am not 100% sure what is the main cause of this problem.
Problem: I am trying to do a basic show/hide DOM element animation by clicking on buttons (one in parent App component, one inside of About section)  and it works almost great. I would like that About section will be out of the viewport by default but after TweenMax.set(...) I still can see About section (I don't use CSS transform for default state).
I saw examples and other topics on the forum but haven't found something like that so far.
I also figured out that there is a workaround: put extra conditions in componentDidUpdate() method and replace all this.aboutSection refs by "id/data attribute/class name" (see code snippet).
 

class App extends Component {
  constructor() {
    super();

    this.state = {
      isOpenedAbout: false
    }
  }

  toggleAboutSection = () => {
    this.setState({isOpenedAbout: !this.state.isOpenedAbout});
  };

  render() {
    return (
      <div>
        <About
          isOpenedAbout={this.state.isOpenedAbout}
          toggleAboutSection={this.toggleAboutSection}
        />
      </div>
    )
  }
}

class About extends Component {
  constructor() {
    super();
    this.aboutSection = null;
  }

  componentDidMount() {
    console.log('this.aboutSection', this.aboutSection);
    // in the browser console: <section class="about text-c-mercury-light">
    TweenMax.set(this.aboutSection, {xPercent: 100});
    console.log('this.aboutSection', this.aboutSection);
    // in the browser console: <section class="about text-c-mercury-light" style="transform: translate(100%, 0%) matrix(1, 0, 0, 1, 0, 0);">
  }

  componentDidUpdate(prepProps) {
    if (this.props.isOpenedAbout && this.props.isOpenedAbout !== prepProps.isOpenedAbout) {
      TweenMax.to(this.aboutSection, .8, {xPercent: 0, ease: Power4.easeInOut});
    } else if (!this.props.isOpenedAbout && this.props.isOpenedAbout !== prepProps.isOpenedAbout) {
      TweenMax.to(this.aboutSection, .8, {xPercent: 100, ease: Power4.easeInOut});
    } else {
    // workaround: if use id/dataAttribute/class instead of ref
    // TweenMax.set(this.aboutSection, {xPercent: 100});
    }
  }

  clickCloseButton = () => {
    this.props.toggleAboutSection();
  };


  render() {
    return (
      <div>
        <section ref={node => (this.aboutSection = node)} className="about text-c-mercury-light">
         ...
        </section>
      </div>
    );
  }
};

 

Link to comment
Share on other sites

Hi and welcome to the GreenSock forums.

 

It would be very helpful to see a live reduced sample in either Codepen or StackBlitz, in order to find the issue faster.

 

What I see in your snippets everything seems to be ok, perhaps the only change I would do, in order to keep things cleaner and easier to read would be to create an instance of the animation in the componentDidMount hook and toggle it's reversed state, something like this:

 

  constructor() {
    super();
    this.aboutSection = null;
    this.aboutTween = null;
  }

  componentDidMount() {
    // HERE x:"100%" IS A SHORTHAND FOR xPercent
    TweenMax.set(this.aboutSection, {x: "100%"});
    this.aboutTween = TweenMax.to(this.aboutSection, .8, {x: 0, ease: Power4.easeInOut}).reversed(true);
  }

  componentDidUpdate(prepProps) {
    // since it's a boolean, use that to change the reverese state of the GSAP instance
    if (this.props.isOpenedAbout !== prepProps.isOpenedAbout) {
      this.aboutTween.reversed(!this.props.isOpenedAbout);
    }
  }

 

While in this subject, also I prefer to set initial state of a DOM element or component using CSS and tween the position using GSAP, because sometimes you could see flashes of the element before the componentDidMount hook is triggered, it would be as simple as using:

 

.about {
  transform: translateX(100%);
}

 

Here you can see a simplified sample for another forums thread that uses x: "100%"  for the animation in a React App:

 

See the Pen ?editors=0010 by rhernando (@rhernando) on CodePen

 

Finally, since you're trying to animate complete components, which could be related to navigation controls, you could use React Router and React Transition Group to create those animations. Is not too complex, take a look at this and the samples in it to see how it can be done:

 

https://greensock.com/react

 

Happy Tweening!!!

  • Like 4
Link to comment
Share on other sites

Hey, thanks for the quick response and detailed examples. I tried your simplification in my code and didn't have a success the animation didn't trigger at all, but in your codepen everything was OK. So I struggled a bit and released that the problem  can be in my High ordered component (didn't know that it would be an important point), I had wrappers like this in my app to decrease the amount of code for fetching data:

export default withFetching(URL_PATH_PERSON_DATA, About);

I printed out steps of lifecycle and found in the console next order:
1) componentDidMount was triggered in About
2) componentDidMount was triggered in HOC

3) componentDidUpdate was triggered in About

4) componentDidUpdate was triggered in HOC
When I removed HOC, the animation starts work

I created a codepen example with this problem.

See the Pen yRqyzJ?editors=0010 by egor-sorokin (@egor-sorokin) on CodePen


Since it's my own application I can remove HOC, but for the future would like to know whether it's an issue of GSAP or React or maybe it's expected behaviour.

Link to comment
Share on other sites

Hi,

 

I'm seeing two issues here. First your conditional logic is never truthy:

 

  componentDidUpdate(prepProps) {
    // this always comes back as false, regardless of the value
    if (this.props.isOpenedAbout !== prepProps.isOpenedAbout) {
      // this block of the code is never reached
    }
  }

 

This basically has to do with the fact that your component is not re-rendered everytime the parent's state is updated. In fact is re-mounted, so everytime the state is updated, the child component is re-mounted, so the previous props is always equal to the current, because there is no change at all.

 

I'm not an expert in HOC, in fact I try to avoid them as much as possible, so I can't help you regarding that. For re-usable components or re-usable code try always composition over inheritance, or instead of using HOC try extending a previously created component or just pass props or child elements to the component you're using as base.

 

Ideally I'd like to know what you're trying to achieve, so I can understand why you're using HOC in this case. In particular, why you need to fetch data in two different components?. If you need two different sets of data just create a helper function that accepts the parameters each request needs and use that on each component. Honestly unless you need to use extremely different setups on each request, I don't see the need of that. Also if you're going to use the data of home and about always in your app, you can use Axios, and use async/await or a Promise.all() method to get all the data in the main component and then pass that data as a prop to each child component:

 

https://github.com/axios/axios

 

Here are a couple of links, for React's docs about composition vs inheritance:

 

https://reactjs.org/docs/composition-vs-inheritance.html

 

And this crazy guy's explanation about the principle of composition vs inheritance in Javascript (well actually He's not crazy... I think :D ):

 

 

Try cleaning up your code a bit and go through those resources and let us know how it goes.

 

Happy Tweening!!

  • Like 4
Link to comment
Share on other sites

Thanks for links and video ?.
Yep, I use axios already, basically, my HOC takes two params URL and Component, then it requests data by given URL and put result into props of the component and returns it, like so:
 

const withFetching = (url, CurrentComponent) =>
  class WithFetching extends Component {
    constructor(props) {
      super(props);

      this.state = {
        data: {},
        isFetching: false,
        error: null,
      };
    }

    componentDidMount() {
      this._fetchData();
    }

    _fetchData() {
      this.setState({ isFetching: true });
     
      axios.get(DEFAULT_API_URL + url)
        .then((response) => {
          if (response.status === 200) {
            return response.data;
          }

          throw new Error('Something went wrong ...');
        })
        .then(data => this.setState({ data, isFetching: false }))
        .catch(error => this.setState({ error: error.message, isFetching: false }));
    }

    render() {
      return (
        <CurrentComponent {...this.props} {...this.state} />
      );
    }
  };


export default withFetching;


I have my own backend, so the main structure of the response is the same for every component, by this reason I decided to create only one fetchData function and a HOC to wrap any component and related to it URL. But yeah probably, in this case, it's better to create just a helper function + it's much easier to test it.

Link to comment
Share on other sites

I would create just one function for the request that takes the url and returns the axios promise. Then handle that promise on each component. Just a couple of extra lines in each component is not going to turn your app into spaghetti code.

 

IMHO you are over-complicating things with this approach and is resulting in React-related issues. A parent state update shouldn't, in any case, result in a child component being mounted again, unless that is specifically the case. In your app it doesn't seem to be what you're trying to do (unless I'm mistaken), so this app structure is backfiring at you.

 

Just my two cents ;)

 

Happy tweening!!!

  • Like 1
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.
×
×
  • Create New...