Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Typescript infer a different type inside setTimeout

The following snippet gives me a type error:

useEffect(() => {
   if(!dragWrapperRef || !dragWrapperRef.current) return;
   setTimeout(() => {
      setDragWrapperHeight(dragWrapperRef.current.getBoundingClientRect().height + 'px');
   },500);
},[]);

current: HTMLDivElement | null. 'dragWrapperRef.current' is possibly 'null'.

const dragWrapperRef: RefObj | null = useRef(null);
type RefObj = {
    current: HTMLDivElement | null
}

The type for dragWrapperRef is correctly inferred if I use dragWrapperRef.current outside of setTimeout. This does not throw an error:

useEffect(() => {
   if(!dragWrapperRef || !dragWrapperRef.current) return;
   setDragWrapperHeight(dragWrapperRef.current.getBoundingClientRect().height + 'px')
},[]);

It does work inside setTimeout if I assign the ref object to another variable outside of the setTimeout ((property) current: HTMLDivElement):

useEffect(() => {
   if(!dragWrapperRef || !dragWrapperRef.current) return;
   const el = dragWrapperRef.current;

   setTimeout(() => {
       setDragWrapperHeight(el.getBoundingClientRect().height + 'px')
   },1000);
},[]);

Why is the type not recognized inside the scope of setTimeout if I pass it in directly – but behaves correctly if assigned to another variable?

like image 604
mirja-t Avatar asked Feb 20 '26 13:02

mirja-t


1 Answers

Because things could have changed in the half-second between when you did the checks and the time the timer fires. (In the more general case: TypeScript doesn't try to track type narrowing of properties/variables that can change into callbacks. This is just a specific example.)

To avoid the error, you could assign to a local const (but keep reading):

useEffect(() => {
    const current = dragWrapperRef?.current;
    if (current) {
        setTimeout(() => {
            setDragWrapperHeight(current.getBoundingClientRect().height + "px");
        }, 500);
    }
}, []);

...but TypeScript is right, in the half-second between grabbing the value and the timer callback running, the ref could have changed! Meaning the code would be using an out-of-date ref. Since that's the case, you might want to just repeat the check:

useEffect(() => {
    if (dragWrapperRef?.current) {
        setTimeout(() => {
            const current = dragWrapperRef?.current;
            if (current) {
                setDragWrapperHeight(current.getBoundingClientRect().height + "px");
            }
        }, 500);
    }
}, []);
like image 90
T.J. Crowder Avatar answered Feb 22 '26 06:02

T.J. Crowder



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!