bitworking / react-gsap

React components for GSAP
https://bitworking.github.io/react-gsap/
Other
583 stars 34 forks source link

[error] this.tween.kill is not a function #58

Closed djeglin closed 2 years ago

djeglin commented 2 years ago

I have a react-gsap timeline with a set of tweens that sits within a react-scrollmagic scene. The duration of this scene is set by watching the viewport height, doing some math and setting the resulting value on the <Scene> element.

This works wonderfully when the page loads, and everything runs just as I would expect it to. However, when I try to resize the browser (or rotate a mobile browser to a different orientation, as this triggers the resize event), the <Tween> components fire a tween.componentWillUnmount event which tries to call this.tween.kill(), which is where I am getting the errors.

Not that it will mean a lot without styling etc, but for reference this is the code I'm using for the component I'm using this effect on, just in case there is something simple that I'm not doing that would fix this. I've made sure I'm using React.forwardRef() where I'm using components, but that doesn't seem to make a difference to this.

import React, { useState, useEffect } from 'react'
import { Link } from 'gatsby'
import { Controller, Scene } from 'react-scrollmagic'
import { Tween, Timeline } from 'react-gsap'
import PropTypes from 'prop-types'

import BlockContent from '../../shared/blockContent'
import Image from '../../image/image'
import { useViewport } from '../../../containers/useViewport'

import './heroHomepage.css'

const HeroHomepage = React.forwardRef(({ data }) => {
  const { height } = useViewport()
  const [ht, setHt] = useState(Number(height))

  useEffect(() => {
    setHt(Number(height))
  }, [height])

  const handleSkip = e => {
    e.preventDefault()
    e.stopPropagation()
    document.querySelector('#postHero').scrollIntoView({ behavior: 'smooth' })
  }

  const Wrapper = React.forwardRef(({ children, index }) =>
    index === 0 ? (
      <>
        {children}
        <Tween to={{ y: '0%' }} />
      </>
    ) : (
      <>
        <Tween from={{ y: '100%' }} to={{ y: '0%' }}>
          {children}
        </Tween>
        <Tween to={{ y: '0%' }} />
      </>
    )
  )

  const RenderItem = React.forwardRef(({ item, index }) => {
    const link =
      item.linkType === 'caseStudy' ? `/work/${item.caseStudy.slug.current}` : item.customLink

    const createLogo = () => ({ __html: item.caseStudy.svgLogo })

    return (
      <Wrapper index={index}>
        <Link className="heroHomepage--item" id={item._key} to={link}>
          <Image file={item.image} alt={item.image.alt || 'Hero image'} />
          <div className="content">
            <h2>{item.title}</h2>
            <div className="text">
              <BlockContent input={item.text} />
            </div>
            {item.linkType === 'caseStudy' && (
              <span className="logo" dangerouslySetInnerHTML={createLogo()} />
            )}
          </div>
        </Link>
      </Wrapper>
    )
  })

  return (
    <Controller>
      <Scene
        duration={() => ht * 2 * data?.Items?.length}
        pin={{ pushFollowers: true }}
        triggerHook="onLeave"
      >
        <Timeline wrapper={<div id="heroHomepage" className="heroHomepage" />}>
          <div className="inner">
            {data?.Items?.map((item, index) => (
              <RenderItem item={item} index={index} />
            ))}
            <a href="#postHero" className="skip" onClick={e => handleSkip(e)}>
              <svg
                width="32"
                height="20"
                viewBox="0 0 32 20"
                fill="none"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  d="M30.5352 2.1123L16.2678 16.5205L2.0004 2.11231"
                  stroke="#F2F2F2"
                  stroke-width="4"
                  stroke-miterlimit="10"
                />
              </svg>
            </a>
          </div>
        </Timeline>
      </Scene>
      <span id="postHero" />
    </Controller>
  )
})

HeroHomepage.propTypes = {
  data: PropTypes.object,
}

export default HeroHomepage

From what I can tell, this isn't so much an issue with the viewport size changing as it is an issue with the unmount caused by using a changing value for the duration on a scrollmagic <Scene>. I didn't really know which library to post this issue in but, as the error itself is occurring within react-gsap, I figured we would start here. For now I can work around this issue by only getting the height once when the component is mounted, rather than it being reactive, but it would be good to get this working, and this seems like it could be a fairly common issue.

bitworking commented 2 years ago

This is an interesting one. Currently I have little time to check the exact issue. Just a question: Why you are using scrollmagic? react-gsap also includes a ScrollTrigger component which is a modern version of scrollmagic. I am not sure if you can prevent the error in this case but should be more performant and maybe easier to integrate: https://bitworking.github.io/react-gsap/src-components-scroll-trigger

djeglin commented 2 years ago

Hmm. So, the reason for using ScrollMagic is because we've kind of got a compound effect going on - The wrapper element pins in place (scrollmagic) and then the slides within work on the timeline (gsap). You can see the effect in action on a preview here: https://deploy-preview-5--flamboyant-payne-83a356.netlify.app/

I'm not 100% sure how I would go about using react-gsap to achieve the combination of temporarily pinned element and ScrollTriggers within. It would be an interesting experiment to try and figure that out, but I'm a bit time poor in the same way you are :)

bitworking commented 2 years ago

With the ScrollTrigger and the pin prop you can achieve exactly the same and it plays more nicely together with the Timeline or Tween component.

Have a look here: https://stackblitz.com/edit/react-gsap-scrolltrigger01?file=index.tsx

https://stackblitz.com/edit/react-gsap-scrolltrigger03?file=index.tsx

bitworking commented 2 years ago

Also have a look at the official docs: https://greensock.com/docs/v3/Plugins/ScrollTrigger

Valeryia7719 commented 2 years ago

@djeglin Hello! Did you find the answer?

djeglin commented 2 years ago

@Valeryia7719 We ended up not using the effect, actually, but I think the solution we tried going for was spending a bit more time and using react-gsap directly for everything, rather than using scrollmagic.