Jump to content
GreenSock

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

Issue with ScrollTrigger pinning in Gatsby/React site

Recommended Posts

Hello,

I am experiencing this error:  'NotFoundErrror: Failed to Execute 'removeChild' on 'Node': The node to be removed is not a child of this node.'

Specifically when using the 'pinning' attribute.

 

Here is the code snippet being used in my component causing the error:

 

useEffect(() => {
	panelRefs.current.forEach((panelRef, index) => {
		// GSAPs default 'end' value.		let endValue = 'bottom top';
		// Unpin the last item right away so it doesn't cover the non-hero content below it.
		// Still apply scrollTrigger to it so it will 'layer' over the prev image.
		if (index === panelRefs.current.length - 1) endValue = 'top top';		

                ScrollTrigger.create({
			trigger: panelRef,
			scrub: true,
			start: 'top top',
			end: endValue,
			markers: true,
			pin: true, // BREAKS
			pinSpacing: false,
		});
	});
});
Thank you in advance
Link to comment
Share on other sites

Hey Frank. Can you please create a minimal demo of the issue using something like CodeSandbox?

 

Link to comment
Share on other sites

Yeah, it's super difficult to troubleshoot blind. @FrankRuiz what version of ScrollTrigger are you using? Please make sure it's the latest. Also, did you gsap.registerPlugin(ScrollTrigger)?

 

Once we see a minimal demo, I'm sure we'll be able to offer more assistance. 👍

Link to comment
Share on other sites

Thanks for your reply guys, here is some more context. 

Here's a (seemingly) working example of what we're doing.


demo page:  '/pages/hero-ex-page.js'
component:  '/components/hero.js'


This code is from our actual project and just stripped down. What we're trying to understand lies in that this very implementation will break when the component is used on a specific page, but not on others. Specifically, when 'pin: true' is set.


I know that's vague and difficult to help troubleshoot and that's completely understandable. If anyone has any insight into experiencing the
'NotFoundErrror: Failed to Execute 'removeChild' on 'Node': The node to be removed is not a child of this node.'


when using GSAP in general, that would also help. And/or if there's anything within the implementation in the '/components/hero.js' component that raises any flags.


*we know this may be a react specific question

 

https://codesandbox.io/s/agitated-waterfall-8g3ep?file=/src/components/hero.js

Link to comment
Share on other sites

I would double check what panelRef is in your project. In the code you provided in your first post here you have panelRef but in the working demo you have panelRef.current. It should probably be panelRef.current

  • Like 1
Link to comment
Share on other sites

Hey Frank,

 

You're not passing any dependencies to the useEffect hook. Is this component rendered just once in the entire lifecycle of the App? Because right now that will run everytime a state or prop in that component is updated or the parent component is re-rendered as well.

 

Also clean up your component when it unmounts by returning a function in a useEffect hook with an empty array as dependencies:

useEffect(() => {
  // At the end of the hook return a function
  return () => {
  	// Here kill all your GSAP instances in case one of them is still running
    // when the component is unmounted
  };
}, []);

Also I'm not seeing any errors in the sample you provided, the only thing that caught my attention is this:

if (panelRefs.current.length !== numPanels) {
  panelRefs.current = Array(numPanels)
    .fill()
    .map((_, i) => {
      return panelRefs.current[i] || createRef()
    })
}

This code is running every time the component is rendered, if at that point the DOM is actually updated as well, wouldn't the references to the DOM elements be updated then in the array map method? My point being, I don't see the real use for this code, even if the panelRefs are kept through re-renders, those elements in the array would be updated on the re-renders. In fact if for some reason at some point the amount of panels is less than in a previous render, you'll keep the reference in the array even though the DOM node is no longer in the DOM, thus creating a potential memory leak. Of course you know your code far better than I do, but this particular snippet stood out.

 

Beyond that I couldn't tell what the problem could be.

 

Happy Tweening!!!

  • Like 2
Link to comment
Share on other sites

  • 2 weeks later...

Thank you for your time guys, it ended up being due to a package that was being used on the component for unique ids that seemed to be mixing up the node references. This had been previously been used as the 'key' prop on the node list. 😅

  • Thanks 1
Link to comment
Share on other sites

  • 5 months later...

Hi @FrankRuiz,

 

I'm having the exact same issue. When the component is unmounted I get the error

 

Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

 

This is the useEffect() function

 

useEffect(() => {
const pin = gsap.to(pinnedRef.current, {
duration: .3,
scrollTrigger: {
trigger: pinnedRef.current,
start: "top top",
end: "200%",
pin: true,
pinReparent: true,
pinSpacing: "100%"
}
});
return () => {
pin.kill();
console.log('killed component');
}
}, []);

 

I know it has been a long time ago, but are you able to give more details on how you resolved the issue? 

 

These are the packages I am importing in the component

 

Thank you!

 

import React, { useState, useRef, useEffect } from 'react';
import { connect, styled } from 'frontity';
import { gsap } from 'gsap';
import ArrowLink from '../UI/arrowLink';
import SwiperCore, { Pagination, Controller, EffectFade, Mousewheel } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/react';
import Animation from '../UI/animation';
import svg2 from '../../assets/img/animation-homepage-2.svg';
import tw from 'twin.macro';
Link to comment
Share on other sites

Hey @eviljeff

 

My issue seems to have been different than yours. However, I think what you are getting is because you aren't cleaning up/killing the ScrollTrigger correctly in your useEffect.  In my code I assign an id to the ScrollTrigger instance and then kill it like so:
 

	useEffect(() => {
		if (titleEl.current) {
			gsap.to(titleEl.current, {
				x: -titleEl.current.offsetWidth * 0.5,
				ease: 'none',
				scrollTrigger: {
					id: `${ ns }-title`,
					trigger: titleEl.current,
					start: 'top bottom',
					end: 'bottom top',
					scrub: 0.25,
				},
			});
		}

		return () => {
			// clean up ScrollTrigger instance on title element
			const titleElTrigger = ScrollTrigger.getById(`${ ns }-title`);

			if (titleElTrigger) {
				titleElTrigger.kill();
			}
		};
	}, []);

 

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Hi @FrankRuiz,

 

Thank you so much for the quick reply!

 

I have tried that out just now but I still get the same error Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node. 

 

Below is my full component, do you see any other issue that could cause the error to show up? If I comment out pin: true then the error won't appear.

 

I truly appreciate your help. Thanks

 

import React, { useState, useRef, useEffect } from 'react';
import { connect, styled } from 'frontity';
import { gsap } from 'gsap';
import { ScrollTrigger } from "gsap/ScrollTrigger";
import ArrowLink from '../UI/arrowLink';
import SwiperCore, { Pagination, Controller, EffectFade, Mousewheel } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/react';
import Animation from '../UI/animation';
import svg2 from '../../assets/img/animation-homepage-2.svg';
import tw from 'twin.macro';

SwiperCore.use([Pagination, Controller, EffectFade, Mousewheel]);

const VerticalSlider = ({ content, libraries, data }) => {
    const [firstSwiper, setFirstSwiper] = useState(null);
    const [secondSwiper, setSecondSwiper] = useState(null);
    const pinnedRef = useRef(null);

    const { slides } = content;

    const params1 = {
        direction: 'vertical',
        // mousewheel: {
        //     releaseOnEdges: true,
        //     forceToAxis: true,
        //     eventsTarget: ".vertical-slider-wrapper"
        // },
        speed: 1000,
        pagination: {
            clickable: true,
        },
        slidesPerView: 1,
        controller: {
            control: secondSwiper
        }
    };
    const params2 = {
        preloadImages: false,
        lazy: true,
        noSwiping: true,
        effect: 'fade',
        controller: {
            control: firstSwiper
        }
    };

    useEffect(() => {
        if (pinnedRef.current) {
            let ns = Math.random().toString(36).substring(7);
            gsap.to(pinnedRef.current, {
                duration: .3,
                scrollTrigger: {
                    id: `${ ns }-title`,
                    trigger: pinnedRef.current,
                    start: "top top",
                    end: "200%",
                    pin: true,
                    pinSpacing: "100%"
                }
            });
        }
        return () => {
            const titleElTrigger = ScrollTrigger.getById(`${ ns }-title`);

			if (titleElTrigger) {
				titleElTrigger.kill();
			}
        }
    }, []);

    return (
        <VerticalSliderWrapper className="vertical-slider-wrapper" ref={pinnedRef}>
            <VerticalSliderCol tw="z-0">
                <Swiper {...params2} onSwiper={setSecondSwiper}>
                    {slides && slides.map((slide, i) => {
                        const {  image } = slide;
                        return (
                            <SwiperSlide key={i.toString()}>
                                <ImageSlide bg={image} />
                            </SwiperSlide>
                        )
                    })}
                </Swiper>
            </VerticalSliderCol>
            {data.isHome && 
                <SvgWrapper>
                    <Animation svg={svg2} elId="emy803gonfxd1" />
                </SvgWrapper>
            }
            <VerticalSliderCol tw="z-20">
                <Swiper {...params1} onSwiper={setFirstSwiper}>
                    {slides && slides.map((slide, i) => {
                        const {  copy, heading, link_text, link_url } = slide;
                        const Html2React = libraries.html2react.Component;
                        const linkUrl = libraries.source.normalize(link_url);
                        return (
                            <SwiperSlide key={i.toString()}>
                                <ContentSlide>
                                    <div>
                                        <p>{heading}</p>
                                        <Html2React html={copy} />
                                        {linkUrl && <ArrowLink link={linkUrl} text={link_text} className="arrow-green" />}
                                    </div>
                                </ContentSlide>
                            </SwiperSlide>
                        )
                    })}
                </Swiper>
            </VerticalSliderCol>
        </VerticalSliderWrapper>
    );
};

export default connect(VerticalSlider);

const VerticalSliderWrapper = tw.div`
    relative flex flex-wrap md:flex-nowrap pt-10 md:pt-20 max-w-full max-h-screen
`;

const VerticalSliderCol = tw.div`
    md:w-1/2
`;

const ImageSlide = styled.div`
${tw`bg-cover h-full w-full`}
    background-image: url(${props => props.bg})
`;

const ContentSlide = styled.div`
    ${tw`p-10 md:p-14 md:pr-20 2xl:p-20 2xl:pr-40 flex items-center h-full`}
`;

const SvgWrapper = styled.div`
    ${tw`absolute w-3/5 z-10 pointer-events-none`}
    right: 25vw;
    top: -25vh;
`;

 

Link to comment
Share on other sites

@eviljeff

 

In your useEffect try just adding a check 

 

	const pinnedRefEl = pinnedRef.current;
	if (!pinnedRefEl) return;

	// rest of your animation code

 

Link to comment
Share on other sites

Thank you. I've tried that but unfortunately it doesn't fix it. The animation doesn't fire when navigating away but I still get the same error. 

 

I believe that the .kill() function doesn't really work..? If I console log titleElTrigger before and after the kill it looks like the ScrollTrigger is still alive. Is that correct?

 

return () => {
    const titleElTrigger = ScrollTrigger.getById(`${ ns }-title`);

    if (titleElTrigger) {
        console.log(titleElTrigger);
        titleElTrigger.kill();
        console.log('After the kill =======');
        console.log(titleElTrigger);
    }
}

// the console logs look identical
ScrollTrigger {start: 5034, progress: 0, media: undefined, scroller: Window, scroll: ƒ, …}
After the kill =======
ScrollTrigger {start: 5034, progress: 0, media: undefined, scroller: Window, scroll: ƒ, …}

 

Thank you so much for the help.

Link to comment
Share on other sites

@eviljeff hmm not sure, it should be removing that ScrollTrigger instance, maybe try just hard coding a string ID and not using a random one. 

Logging this will show you all the accumulated ST  instance - ScrollTrigger.getAll( )

Link to comment
Share on other sites

I did try hard coding a string ID and that didn't make a difference. If I run ScrollTrigger.getAll() on the useEffect unmount I get all of my 4 ScrollTrigger instances. Should I see 3 in the array if the kill function I'm trying to run would execute correctly? The one I'm trying to kill is the second one in the array.

 

1410841964_Screenshot2021-04-02at21_11_53.thumb.png.2964d6eeccc579b5260159507d1e748a.png

 

Link to comment
Share on other sites

  • 2 weeks later...

Hi @FrankRuiz

 

I've still not resolved this issue unfortunately.

 

I've read that your issue was being due to a package. Could you please let me know what package that was? I'm using Frontity, which is a framework a bit like Gatsby so perhaps they use similar packages.

 

Thanks you

Link to comment
Share on other sites

  • 4 weeks later...

Hi @FrankRuiz,

 

Have you tried surrounding your pinnedRef  with a <div> ... </div> ?

 

<div>
   <VerticalSliderWrapper className="vertical-slider-wrapper" ref={pinnedRef}>
      ...
   </VerticalSliderWrapper>
</div>

 

I have a similar issue when changing routes on react router, and this simple change fixed my problem.

 

Cheers

  • Thanks 1
Link to comment
Share on other sites

  • 3 weeks later...

For anyone else coming across this problem, the solution is to use useLayoutEffect as shown in this post.

 

 

 

  • Like 2
  • Thanks 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.
×