Jump to content
GreenSock

Marina Gallardo

GSAP & React error: setTimeout is not supported in server-rendered Javascript

Recommended Posts

Hi community ,

 

this is my first project with React and GSAP. I really love the work we have created, but we have a big issue: 
 

In the project, we are using server side render with reactjs.NET. When we try to render a component with the gsap library we got this error:

 

setTimeout is not supported in server-rendered Javascript


Here is an image with the error too.

 

please, help me with some ideas, I made great things with gsap and I dont want to dont  finally use the library and I have to finish the project next week... 

 

thank you very much 

AE4F306C-34E8-4CBA-AAAD-9C0A9F0F3DBE.png

Link to comment
Share on other sites

Lots of stuff won't be supported server side. You need to make sure the JS runs client side, but I'm not sure how to do that with the framework you're using. It's probably best to ask the people who make it, like how to run JS when the window object is available.

  • Like 2
Link to comment
Share on other sites

And I have a feeling that you may need to do something like mentioned here with server side rendering.

 

 

That process.client property might unique to nuxt/vue. However, there might something similar to the framework you are using.

 

  • Like 2
Link to comment
Share on other sites

Hi @Marina Gallardo,

 

What does your setup look like? How are you creating/running your animations?

 

Most of the sites we create are SSR React using GSAP.

Link to comment
Share on other sites

I am using yarn install for GSAP library, this is the component we are trying to make SSR and the animation setup:

import React, { useState, useEffect, useRef } from 'react';
import { PropTypes as pt } from 'prop-types';
import { useSliderSwipe, useObserver } from 'React/custom_hooks';
import { TimelineMax, TweenMax } from 'gsap/all';
 
import './style.scss';
 
import { TagTitle, InputRangeSlider, Link } from 'React/components';
 
const CifrasSlider = ({ title, frames, module_title, lead_text, module_description, module_cta }) => {
const [activeFrame, setActiveFrame] = useState(1);
 
const imagesContainerWidth = { width: `${100 * (frames.length + 1)}vw` };
const headerSliderComponent = React.createRef();
const cifrasRef = React.createRef();
 
if (frames.length > 1) {
useSliderSwipe(headerSliderComponent, setActiveFrame, frames.length);
}
 
const [observer, setElements, entries] = useObserver({
root: null,
threshold: 0.5,
});
 
const tl = new TimelineMax({ delay: 0, repeat: 0 });
 
useEffect(() => {
 
cifrasRef.current.firstElementChild.querySelector('.number-inner').classList.add('slow--y');
cifrasRef.current.firstElementChild.querySelector('.title--xs').classList.add('slow--y');
 
const elements = headerSliderComponent.current.querySelectorAll('.slow--y');
 
TweenMax.set(elements, { opacity: 0, y: 90 });
setElements(elements);
}, [setElements]);
 
useEffect(() => {
entries.forEach((entry) => {
// buscamos los elementos que son visibles y entonces aplicamos la animación.
if (entry.isIntersecting) {
let lazyItem = entry.target;
tl.to(lazyItem, 1, { opacity: 1, y: 0 });
observer.unobserve(lazyItem);
}
});
}, [entries, observer, TimelineMax]);
 
return (
<div className="module grid cifras-slider" data-active-frame={activeFrame} ref={headerSliderComponent}>
<TagTitle style="slow--y" title={title} />
{module_title && <h3 className="module_title title--l slow--y">{module_title}</h3>}
{lead_text && <h4 className="lead_text body--l slow--y">{lead_text}</h4>}
{module_description && (
<p className="module_description body--m">
{module_description}
{module_cta && (
<div className="module_cta">
<Link path={module_cta.path} type="arrow">
{module_cta.label}
</Link>
</div>
)}
</p>
)}
 
<div ref={cifrasRef} className="cifras-container" style={imagesContainerWidth}>
{frames.map((frame, idx) => {
const { value, unit, caption } = frame;
return (
<div className="data-point" key={`data-point-${idx}`}>
<h3 className="number cifras--xl">
<div className="number-container">
<div className="number-inner">
{value}
{unit && <small>{unit}</small>}
</div>
<p className="title--xs short-descritpion">{caption}</p>
</div>
</h3>
</div>
);
})}
</div>
 
{frames.length > 1 ? (
<InputRangeSlider
changeHandler={setActiveFrame}
framesCount={frames.length}
activeFrame={parseInt(activeFrame)}
description={module_description}
frameDescription={frames[activeFrame - 1].descr}
/>
) : (
<hr className="separator slow--y" />
)}
</div>
);
};
 
CifrasSlider.propTypes = {
title: pt.string,
frames: pt.arrayOf(
pt.shape({
value: pt.string.isRequired,
unit: pt.string,
caption: pt.string,
descr: pt.string,
})
).isRequired,
module_title: pt.string,
lead_text: pt.string,
module_description: pt.string,
module_cta: pt.shape({
path: pt.string.isRequired,
label: pt.string.isRequired,
}),
};
 
export default CifrasSlider;

 

 

Then we are rendering at server through this plugin:

https://reactjs.net/

 

Thank you for your support, I really don't know how to make it work or do not render that part and import the library once is rendered...  I also have webpack.config.

 

Link to comment
Share on other sites

Hi @Marina Gallardo,

 

Check with version of GSAP you're loading. The syntax you're using is all GSAP v2, and you're likely pulling in GSAP3.

Also, I find it best to set my timeline variable in useRef (to help avoid issues with there being no window, etc).

My setup generally looks something like this:

 

 

 

import React, { useEffect, useCallback, useRef } from 'react';
import { useInView } from 'react-intersection-observer';
import gsap from 'gsap';

const tl = useRef();

const animationElementRef = useCallback((node) => {
	const el = node.children;

	tl.current = new gsap.timeline({ paused: true });
  	tl.current
		.to(el, {autoAlpha: 1});
}

const [lazyRef, inView, element] = useInView({
    threshold: 0,
    rootMargin: '-250px 0px',
    triggerOnce: true, // this will unobserve
});

useEffect(() => {
    if (inView) {
      
      tl.current.play();
      
      // or you could very easily do something like this, which is more inline with your code: 
      const el = element.target;
    
      gsap.timeline()
        .to(el, { autoAlpha: 1, duration: 1 })
      	.to(el, { rotation: '360deg'});

		//or just gsap.to(el, {autoAlpha:1, duration: 1}) //no need for a timeline
    }
  
    // cant remember if this needs to go in a seperate useeffect or not
  	return () => {
      tl.current.kill();
    };
}, [inView, element]);


return (
  <CustomComponent ref={animationElementRef}>
	<LazyComponent ref={lazyRef} />
  </CustomComponent>
);

I use https://github.com/thebuilder/react-intersection-observer for my Intersection observer code, but the setup will be very similar with what you have.

 


Please forgive any errors in that code. Most of it is from memory, and is just to give you an idea for how to use GSAP with react and (react) Intersection Observer...

  • Like 3
Link to comment
Share on other sites

Nothing, we confirm that it happens just for importing de library to the component.

Link to comment
Share on other sites

Make sure you have everything setup correctly... client side code separate from server side code.

https://reactjs.net/bundling/webpack.html

 

Or use gsap in a script tag, like from cdnjs.

 

@model IEnumerable<ReactDemo.Models.CommentModel>
@{
    Layout = null;
}
<html>
<head>
    <title>Hello React</title>
</head>
<body>
    @Html.React("CommentBox", new
    {
        initialData = Model,
        url = Url.Action("Comments"),
        submitUrl = Url.Action("AddComment"),
        pollInterval = 2000,
    })
    <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.development.js"></script>
    <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/remarkable/1.7.1/remarkable.min.js"></script>


	<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.1.1/gsap.min.js"></script>

    <script src="@Url.Content("~/js/tutorial.jsx")"></script>
    @Html.ReactInitJavaScript()
</body>
</html>

 

 

  • Like 1
Link to comment
Share on other sites

If i use gsap as a script the way you say, can I run the animations the same way in react? 

Link to comment
Share on other sites

3 hours ago, Marina Gallardo said:

If i use gsap as a script the way you say, can I run the animations the same way in react?

I'm no server side expert so I could be wrong here, but I believe that so long as the bundled JS comes after the import of GSAP via the script tag then the bundle shouldn't care that it's from a script tag.

Link to comment
Share on other sites

3 hours ago, Marina Gallardo said:

If i use gsap as a script the way you say, can I run the animations the same way in react? 

Yes, you just wouldn't need to @import gsap.

Also/again, looking at your code, what version of GSAP are you using?

You may try importing as such:
import gsap from 'gsap';

 

Instead of {timelinemax, tweenmax} from the gsap/all

 

 

Link to comment
Share on other sites

I am using GSAP 3.

 

Just to let you know that adding GSAP the way @OSUblakescript tag solved the SSR issue!

 

Thank you, you saved my life :-D 

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