Marina Gallardo Posted February 19, 2020 Share Posted February 19, 2020 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 Link to comment Share on other sites More sharing options...
OSUblake Posted February 19, 2020 Share Posted February 19, 2020 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. 2 Link to comment Share on other sites More sharing options...
OSUblake Posted February 19, 2020 Share Posted February 19, 2020 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. 2 Link to comment Share on other sites More sharing options...
elegantseagulls Posted February 19, 2020 Share Posted February 19, 2020 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 More sharing options...
Marina Gallardo Posted February 19, 2020 Author Share Posted February 19, 2020 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 More sharing options...
elegantseagulls Posted February 19, 2020 Share Posted February 19, 2020 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... 3 Link to comment Share on other sites More sharing options...
Marina Gallardo Posted February 20, 2020 Author Share Posted February 20, 2020 Nothing, we confirm that it happens just for importing de library to the component. Link to comment Share on other sites More sharing options...
OSUblake Posted February 20, 2020 Share Posted February 20, 2020 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> 1 Link to comment Share on other sites More sharing options...
Marina Gallardo Posted February 20, 2020 Author Share Posted February 20, 2020 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 More sharing options...
ZachSaucier Posted February 20, 2020 Share Posted February 20, 2020 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 More sharing options...
elegantseagulls Posted February 20, 2020 Share Posted February 20, 2020 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 More sharing options...
Marina Gallardo Posted February 20, 2020 Author Share Posted February 20, 2020 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 2 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now