I would expect this useRef and useEffect combo to fail…why does it succeed?

I would expect this useEffect to fail on the first render, since I would assume the innerCarouselRef.current would be undefined on the first render and it makes a call to getBoundingClientRect. Why does it work/why is the innerCarouselRef.current defined when the useEffect runs?

 import React from 'react';
 import { debounce } from 'lodash';

 export default function Carousel({ RenderComponent }) {
  const [innerCarouselWidth, setInnerCarouselWidth] = React.useState(0);
  const [itemWidth, setItemWidth] = React.useState(0);
  const innerCarouselRef = useRef();
  const itemRef = useRef();
  const content = data.map((el, i) => {
    return (
      <div key={`item-${i}`} ref={i === 0 ? itemRef : undefined}>
        <RenderComponent {...el} />
      </div>
    );
  });
  useEffect(() => {
    const getElementWidths = () => {
      setInnerCarouselWidth(innerCarouselRef.current.getBoundingClientRect().width); // why doesn't this call to getBoundingClientRect() break?
      setItemWidth(itemRef.current.getBoundingClientRect().width);
    };
    getElementWidths();
    const debouncedListener = debounce(getElementWidths, 500);
    window.addEventListener('resize', debouncedListener);
    return () => window.removeEventListener('resize', debouncedListener);
  }, []);
 return (
    <div className="inner-carousel" ref={innerCarouselRef}>
      {content}
    </div>
  )
}

Answer

React runs the effects after it has updated the DOM (we typically want it to work that way). In your case, the effect runs after the component has mounted and so innerCarouselRef.current is set.

I would recommend reading the useEffect docs to gain a better understanding.