Jump to content
Search Community

Snapping breaks when re-rendering in React

Rayan Boutaleb test
Moderator Tag

Recommended Posts

Greetings ! 
I'm building a slider that is destined to be modified in terms of items quantity.
I made a fairly simple setup for this slider to work and, without re-rendering, it works as expected.
The problem comes when adding/deleting items in this slider, the snapping stops working, although I am re-building the whole logic on length change, and of course, after reverting the context on react's useEffect's `return`

 

A pile of code will be more obvious to show what is tried to be achieved, so find my codepen hereby.

 

Reproduction: 
- Slide the slider to see snapping work and fit to "current slide"
- Click top left button to add an item 
- See re-rendering happen
- Slide again in the slider
- Snapping doesnt work.

I ran out of ideas of how I could possibly fix this, or work around it.
Any help is welcome ! Thanks
 

See the Pen ExJyOyp by hellothao (@hellothao) on CodePen

Link to comment
Share on other sites

Hi there! I see you're using React -

Proper cleanup is very important with frameworks, but especially with React. React 18 runs in strict mode locally by default which causes your useEffect() and useLayoutEffect() to get called TWICE.

 

Since GSAP 3.12, we have the useGSAP() hook (the NPM package is here) that simplifies creating and cleaning up animations in React (including Next, Remix, etc). It's a drop-in replacement for useEffect()/useLayoutEffect(). All the GSAP-related objects (animations, ScrollTriggers, etc.) created while the function executes get collected and then reverted when the hook gets torn down.

 

Here is how it works:

const container = useRef(); // the root level element of your component (for scoping selector text which is optional)

useGSAP(() => {
  // gsap code here...
}, { dependencies: [endX], scope: container }); // config object offers maximum flexibility

Or if you prefer, you can use the same method signature as useEffect():

useGSAP(() => {
  // gsap code here...
}, [endX]); // simple dependency Array setup like useEffect()

This pattern follows React's best practices.

 

We strongly recommend reading the React guide we've put together at https://gsap.com/resources/React/

 

If you still need help, here's a React starter template that you can fork to create a minimal demo illustrating whatever issue you're running into. Post a link to your fork back here and we'd be happy to take a peek and answer any GSAP-related questions you have. Just use simple colored <div> elements in your demo; no need to recreate your whole project with client artwork, etc. The simpler the better. 

Link to comment
Share on other sites

Hi,

 

The main issue is here:

const length = items.length - 1;

useGSAP(() => {
  const scrollEffect = gsap.to(sliderInner.current, {
    x: function () {
      return window.innerWidth - sliderInner.current.offsetWidth;
    },
    ease: 'none',
    scrollTrigger: {
      scrub: 1,
      start: `top top`,
      end: `bottom+=${
      sliderInner.current.offsetWidth - window.innerWidth
    }px top`,
      pinType: 'fixed',
      pin: list.current,
      scroller: slider.current,
      container: list.current,
      markers: false,
      snap: {
        snapTo: 1 / length,
        delay: 0.05,
        duration: 0.6,
        ease: 'expo.out',
      },
      target: sliderInner.current,
    },
  });

  // Store
  sc.current = scrollEffect;
  setScrollContainer(scrollEffect);
}, [length]);

You're passing length as a dependency, but length is not really reactive so when is updated the DOM is not yet updated. Is better to pass something that is a state or prop dependency and use the revertOnUpdate flag as well.

 

Here is a simple demo that works as expected:

https://stackblitz.com/edit/vitejs-vite-hvfiwn?file=src%2FApp.jsx

 

Happy Tweening!

Link to comment
Share on other sites

Actually, you were totally right @Rayan Boutaleb. There was indeed a bug that would affect snapping if you revert() a context that contains a ScrollTrigger with a scroller defined that's not the window/body. That should be fixed in the next release which you can preview at: 

 

Core: https://assets.codepen.io/16327/gsap-latest-beta.min.js

ScrollTrigger: https://assets.codepen.io/16327/ScrollTrigger.min.js

 

In the meantime, here's a corrected version of your CodePen that shows a workaround in place which was basically recording the _scrollTop value from the scroller element, and restoring it inside the cleanup function: 

See the Pen GRLjKEw?editors=0110 by GreenSock (@GreenSock) on CodePen

 

Does that clear things up? Sorry about any confusion there. 

  • Like 1
Link to comment
Share on other sites

Hello @GreenSock @Rodrigo, and thanks for looking into this !
First approach makes sense, I although did try locally to have the length as a parameter but that didn't help it (using useEffect, so maybe by using useGSAP it will work nicely )


Sounds good for next release, thanks a lot for the quick fix and taking a deeper look into this little edge case !

I'll probably do a combination of all approaches, swap out useEffect for useGSAP  solution everywhere and use the scrollTrigger beta version

Thanks again, will give it a go and mark as resolved once I get it running perfectly ! 

 

  • Like 2
Link to comment
Share on other sites

Sounds good. And you don't have to use the beta if you don't want to - you could just apply that workaround from my CodePen (save/restore the _scrollTop function on the scroller element). 

 

Let us know how it goes. And thanks for being a Club GSAP member! 💚

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...