Search code examples
reactjsgsap

How to run useGSAP hook only after certain elements are rendered in react


so i have this members page where i fetch data from a json and then according to the size of team the width of #membersBox will be set once they are loaded . so the issue over here is i am using gsap to animate it and gsap runs before the width is set thus the variable "H" is set to 0, and my animation dosen't work . how do i solve it? Here is the code:

import { useEffect, useState } from 'react'
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import './membersPage.css'
import data from './members.json'

const membersPage = () => {
gsap.registerPlugin(ScrollTrigger);
useGSAP(() => {
    const H = document.querySelector("#membersBox").clientWidth;
    let mm = gsap.matchMedia();
    mm.add("(max-width: 767px)", () => {
        gsap.to(".teamBox", {
            x: -H - 1300,
            scrollTrigger: {
                trigger: ".teamWrap",
                scroller: "body",
                start: "top 0%",
                end: `top -${H / 5}%`,
                scrub: 2,
                pin: true,
            },
        });
    });
    mm.add("(min-width: 768px)", () => {
        gsap.to(".teamBox", {
            x: -H - 300,
            scrollTrigger: {
                trigger: ".teamWrap",
                scroller: "body",
                start: "top 0%",
                end: `top -${H / 2}%`,
                scrub: 2,
                pin: true,
            },
        });
    });
})

const [team, setTeam] = useState('')
// let [searchParams, setSearchParams] = useSearchParams();
useEffect(() => {
    // setTeam(searchParams.get("team"))
    var len = data['web'].length
    if (len % 2 != 0) {
        len = len + 1
        for (let i = 0; i < len; i++) {
            if (!document.getElementById(`wave${i}`)) {
                if (i == 0) {
                    document.getElementById('wave').innerHTML += `
                    <div className="waveBox">
                    <img id="wave${i}" style="position: relative;" src="/PublicAssets/teamWave.svg" alt="" key=${i} />
                    </div>
                    `
                }
                else {
                    let leftVal = i * 28
                    document.getElementById('wave').innerHTML += `
                    <div className="waveBox">
                    <img id="wave${i}" style="position: relative;left: -${leftVal}px;" src="/PublicAssets/teamWave.svg" alt="" key=${i} />
                    </div>
                    `
                }
            }
        }
    }
    else {
        for (let i = 0; i < len; i++) {
            if (!document.getElementById(`wave${i}`)) {
                if (i == 0) {
                    document.getElementById('wave').innerHTML += `
                    <div className="waveBox">
                    <img id="wave${i}" style="position: relative;" src="/PublicAssets/teamWave.svg" alt="" key=${i} />
                    </div>
                    `
                }
                else {
                    let leftVal = i * 28
                    document.getElementById('wave').innerHTML += `
                    <div className="waveBox">
                    <img id="wave${i}" style="position: relative;left: -${leftVal}px;" src="/PublicAssets/teamWave.svg" alt="" key=${i} />
                    </div>
                    `
                }
            }
        }
    }
    data['web'].forEach((e, i) => {
        if (!document.getElementById(`member${i}`)) {
            if (i == 0) {
                document.getElementById('membersBox').innerHTML += `
                <div className="member" id="member${i}" style="left:70px;flex-direction: column-reverse;bottom:0px">
                <div id="teamDetailCont">
                    <div id="memName">${e.name}</div>
                    <div id="msg">"${e.msg}"</div>
                </div>
                <div id="mask">
                <img id="teamImg" src="${e.img}" alt="" />
                </div>
                </div>
                `
            }
            else {
                var bottom = i % 2 == 0 ? 0 : -250
                var colDirection = i % 2 != 0 ? 'column' : 'column-reverse'
                var Left = Number(document.getElementById(`member${i - 1}`).style.left.replace('px', '')) + 135
                document.getElementById('membersBox').innerHTML += `
                <div className="member" id="member${i}" style="left:${Left}px;flex-direction: ${colDirection};bottom:${bottom}px">
                <div id="teamDetailCont">
                    <div id="memName">${e.name}</div>
                    <div id="msg">"${e.msg}"</div>
                </div>
                <div id="mask"> 
                <img id="teamImg" src="${e.img}" alt="" />
                </div>
                </div>
                `
            }
        }
    });

}, [])
return (
    <>
        <div className='teamWrap'>
            <div className="teamCont">
                <div className="teamHead">
                    <h1>Meet the minds behind the <span style={{ position: 'absolute', paddingLeft: '17px' }} >magic</span></h1>
                    <h1 className='teamNameHead' >The <span className='headHighlight'>{team}</span>{team.toLowerCase() == "board" ? " Members" : " Team"}</h1>
                </div>
                <div className="teamBox">
                    <div id='wave' className="wave"></div>
                    <div id="membersBox"></div>
                </div>
            </div>
        </div>
    </>
)

}

export default membersPage


Solution

  • I have removed imports because I am getting them from a CDN, obviously you can keep your imports ^^

    I have factorized your rendering conditions (less ifs) but it should render the same.

    Avoid at all cost doing innerHTML manipulation while using React ! It seems you did not know how to use loops in react, note the usual:

      <div>
        {
          array.map((element, index) => {
            // compute something if you need to and return the constructed element
            return (
               <span>{element.name /* or whatever with element */}</span>
            );
          })
         }
      </div>
    

    If you do not have an array to begin with (for loop with length),you can construct one on the fly this way:

      [...Array(12)].map((_, index) => {
        // Note that '_' is just a regular variable name,
        // but the value is undefined in this case so
        // I name it '_' to help visually ignore it
    
      })
    

    Since you GSAP code requires your elements to have a size, wait for your component to be mounted. Note what I did with useEffect, setMounted.

    // import { useEffect, useState } from 'react'
    // import gsap from "gsap";
    // import { useGSAP } from "@gsap/react";
    // import { ScrollTrigger } from "gsap/ScrollTrigger";
    // import './membersPage.css'
    // import data from './members.json'
    
    const { useEffect, useState, Fragment } =  React;
    const data = {
      web: [
        {
          name: 'name1',
          msg:'msg1',
          img: 'img1'
        },
        {
          name: 'name2',
          msg:'msg2',
          img: 'img2'
        },
        {
          name: 'name3',
          msg:'msg3',
          img: 'img3'
        }
      ]
    };
    
    // fake useGSAP, remove in your code, useGSAP also allows dependencies
    const useGSAP = useEffect;
    
    const MembersPage = () => {
      const [mounted, setMounted] = useState(false);
      useGSAP(() => {
        if (!mounted) {
          return;
        }
        // gsap.registerPlugin(ScrollTrigger);
        const H = document.querySelector("#membersBox").clientWidth;
        // removed gsap code we are only testing the width (H)
        console.log(H);
      }, [mounted]);
    
      const [team, setTeam] = useState('');
      // let [searchParams, setSearchParams] = useSearchParams();
      useEffect(() => {
        // setTeam(searchParams.get("team"))
        setMounted(true)
        
    
      }, []);
      var len = data['web'].length;
      // if len is odd, make len even by adding 1
      if (len % 2 !== 0) {
          len++;
      }
      return (
          <div className='teamWrap'>
              <div className="teamCont">
                  <div className="teamHead">
                      <h1>
                        Meet the minds behind the
                        <span style={{ position: 'absolute', paddingLeft: '17px' }}>
                          magic
                        </span>
                      </h1>
                      <h1 className='teamNameHead' >
                        The
                        <span className='headHighlight'>{team}</span>
                        {team.toLowerCase() == "board" ? " Members" : " Team"}
                      </h1>
                  </div>
                  <div className="teamBox">
                      <div id='wave' className="wave">
                        {
                          [...Array(len)].map((_, i) => { 
                            // if i === 0 then leftVal === 0
                            let leftVal = i * 28;
                            return (
                              <div class="waveBox">
                                <img
                                  id={`wave${i}`}
                                  style={{ position: 'relative', left: -leftVal }}
                                  src="/PublicAssets/teamWave.svg"
                                  alt="teamWave"
                                  key={i}
                                />
                              </div>
                            );
                            
                          })
                        }
                      </div>
                      <div id="membersBox">
                        {
                          data['web'].map((e, i) => {
                            var bottom = i % 2 === 0 ? 0 : -250;
                            var colDirection = i % 2 !== 0 ? 'column' : 'column-reverse';
                            var Left = 70 + 135 * i;
                            return(
                              <div
                                className="member"
                                id={`member${i}`}
                                style={{ left: Left, flexDirection: colDirection, bottom: bottom }}
                              >
                                <div id="teamDetailCont">
                                    <div id="memName">{e.name}</div>
                                    <div id="msg">"{e.msg}"</div>
                                </div>
                                <div id="mask">
                                  <img id="teamImg" src={e.img} alt={`img${i+1}`} />
                                </div>
                              </div>
                            );
                          })
                        }
                      </div>
                  </div>
              </div>
          </div>
      );
    };
    
    ReactDOM.render(<MembersPage />, document.getElementById('root'));
    /* DO NOT USE THIS CSS, KEEP YOURS*/
    #wave {
      display: flex;
      border: 1px solid violet;
    }
    #membersBox {
      display: flex;
      border: 1px solid red;
    }
    .waveBox {
      border: 1px solid blue;
      /* min-width, min-height to simulate missing image, remove on your side */
      min-width: 100px; 
      min-height: 50px;
    }
    .member {
      border: 1px solid orange;
      min-width: 100px; 
      min-height: 50px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
    <div id="root"></div>