1
votes

server : Node.js, MongoDB, Graphql
front-end: React --typescript, apollo-client, Graphql

I think it's a matter of timing or order to bring data and render it. Nevertheless, I cannot find a solution to this problem.

Error


  1. Uncaught TypeError: Cannot read property 'getBoundingClientRect' of undefined.
  2. Uncaught (in promise) TypeError: Cannot read property 'getBoundingClientRect' of undefined.

Code


import React, { MutableRefObject, useCallback, useLayoutEffect, useRef, useState } from 'react';
import { gql, useQuery } from '@apollo/client';
import { MainSlide } from '../../components';
import { MSlidersItf, MSlideItf } from '../../types';
import * as S from './MainSliderStyle';
import { GetWindowWidth } from '../../api/window-width/WindowWidth';

const GET_MAINSLIDERS = gql`
  query {
    getMainSliders {
      idx
      title {
        en
        ko
      }
      text {
        en
        ko
      }
      url
      src
      postingDate
    }
  }
`;

const SlideMapForm = (item: MSlideItf, i: number, Count: number) => {
  return (
    <MainSlide
      key={String(i + Count)}
      title={item.title}
      text={item.text}
      url={item.url}
      src={item.src}
      postingDate={item.postingDate}
    />
  );
};
export default function TestBtn() {
  const { loading, error, data } = useQuery<MSlidersItf>(GET_MAINSLIDERS);
  const MSlidersLength = data && data?.getMainSliders.length;
  const TotalSlideCount = MSlidersLength! + 4;

  // Ref
  const slidersRef = useRef() as MutableRefObject<HTMLDivElement>;

  // getSize
  const windowWidth: number = GetWindowWidth(); // Browser window width
  const [elWidth, setElWidth] = useState(0);
ERROR----------------------------------------------------------------------------------------
  const getEleWidth = useCallback(() => {
    if (data) {
      const { width } = slidersRef.current.children[2].getBoundingClientRect();
      setElWidth(width + 16); // Add Slide Margin
    }
  }, [data]);
ERROR----------------------------------------------------------------------------------------
  const sliderMargin: number = (windowWidth - elWidth) / 2;

  // Slide to Center
  const [center, setCenter] = useState(2);
  const sliderPosition: number = elWidth * center - sliderMargin; // Slide Position Inital Value

  // sliderControll
  const prevButton = () => setCenter(center - 1);
  const nextButton = () => setCenter(center + 1);

  useLayoutEffect(() => {
ERROR----------------------------------------------------------------------------------------
    getEleWidth();
ERROR----------------------------------------------------------------------------------------
    const resizeLitener = () => {
      const timing = setTimeout(() => getEleWidth(), 150);
      clearTimeout(timing);
    };

    window.addEventListener('resize', resizeLitener);
    window.addEventListener('load', resizeLitener);

    return () => {
      window.removeEventListener('resize', resizeLitener);
      window.removeEventListener('load', resizeLitener);
    };
  }, [TotalSlideCount, center, data, elWidth, getEleWidth, sliderPosition]);

  if (loading) return <h1>Loding...</h1>;
  if (error) return console.log(`MainSliders DB Error ${error}`);
  return (
    <S.Wrap>
      {data && (
        <S.SlidersArea ref={slidersRef} position={sliderPosition}>
          {Object.values(data.getMainSliders)
            .slice(-2)
            .map((item, i) => SlideMapForm(item, i, -MSlidersLength!))}
          {Object.values(data.getMainSliders).map((item, i) => SlideMapForm(item, i, 0))}
          {Object.values(data.getMainSliders)
            .slice(-2)
            .map((item, i) => SlideMapForm(item, i, MSlidersLength!))}
        </S.SlidersArea>
      )}
      <S.Arrows onClick={prevButton}>
        <S.Prev>
          <img src="/img/icon/arrow-next.svg" alt="Previous" />
        </S.Prev>
        <S.Next onClick={nextButton}>
          <img src="/img/icon/arrow-next.svg" alt="Next" />
        </S.Next>
      </S.Arrows>
    </S.Wrap>
  );
}

2

2 Answers

1
votes

try this:

const getEleWidth = useCallback(() => {
  if (data) {
    const defaultValue = { width: 0 };
    const { width } = slidersRef.current?.children?[2]?.getBoundingClientRect?.() || defaultValue; // just check if the element you want exist 
    setElWidth(width + 16);
  }
}, [data, slidersRef.current]); //<=== add slidersRef to your dependencies
1
votes

Apart from checking if the objects exist or not (like Taghi Khavari mentionned), you can just try to give it a timeout so that you can give your dom elements to be rendered properly. It is never the best solution, but it something that can get your script working quickly.

(Going to use Taghi's solution)

const defaultValue = { width: 0 };
setTimeout( () => {
const { width } = slidersRef.current?.children?[2]?.getBoundingClientRect?.() || defaultValue; // just check if the element you want exist 
setElWidth(width + 16);}, 20) // You can change the number of milliseconds depending on your rendering