Home > database >  How to safely check if object contains an HTML node?
How to safely check if object contains an HTML node?

Time:10-19

I have a function which is called on a click event:

  useEffect(() => {
    document.addEventListener('click', (e) => handleClickOutside(e), true);
  });

The function:

  const myElement = useRef(null);

  const handleClickOutside = (e: MouseEvent) => {
    if (!colorPickerRef) {
      return;
    }

    if (!myElement.current?.contains(e.target)) {
         // do something
    }
  };

However I get the error property 'contains' does not exist on type 'never'

How can I promise typescript that myElement.current is not null?

CodePudding user response:

I think that the better way to handle this is to also pass to the useRef a type.

By this I mean something like this : useRef<HTMLDivElement> for example

const myElement = useRef<HTMLDivElement>(null)

const handleClickOutside = (e: MouseEvent) => {
    if (!myElement.current) {
        // If myElement is null then we return
        return
    }
    
    if (!myElement.current.contains(e.target)) {
            // do something
    }
}

CodePudding user response:

Problem

The type for useRef looks like this:

    /**
     * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
     * (`initialValue`). The returned object will persist for the full lifetime of the component.
     *
     * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
     * value around similar to how you’d use instance fields in classes.
     *
     * @version 16.8.0
     * @see https://reactjs.org/docs/hooks-reference.html#useref
     */
    function useRef<T>(initialValue: T): MutableRefObject<T>;
    // convenience overload for refs given as a ref prop as they typically start with a null value
    /**
     * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
     * (`initialValue`). The returned object will persist for the full lifetime of the component.
     *
     * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
     * value around similar to how you’d use instance fields in classes.
     *
     * Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type
     * of the generic argument.
     *
     * @version 16.8.0
     * @see https://reactjs.org/docs/hooks-reference.html#useref
     */
    function useRef<T>(initialValue: T|null): RefObject<T>;
    // convenience overload for potentially undefined initialValue / call with 0 arguments
    // has a default to stop it from defaulting to {} instead
    /**
     * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
     * (`initialValue`). The returned object will persist for the full lifetime of the component.
     *
     * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
     * value around similar to how you’d use instance fields in classes.
     *
     * @version 16.8.0
     * @see https://reactjs.org/docs/hooks-reference.html#useref
     */
    function useRef<T = undefined>(): MutableRefObject<T | undefined>;

reference

It either returns a RefObject or a MutableRefObject. They essentially have the same shape and look like this:

    interface RefObject<T> {
        readonly current: T | null;
    }

reference

    interface MutableRefObject<T> {
        current: T;
    }

reference

From the above you can see the type of current takes on the type of the argument useRef is called with. You called useRef with null, therefore T has a type of null, therefore current has a type of null. The contains method cannot be called on null hence the TypeScript error.

Solution

useRef accepts a type parameter (see generics) which allows you to give a type to T yourself. You might do something like this (replace HTMLElement with a type more specific to your case):

import { useRef } from "react";

const myElement = useRef<HTMLElement>(null);

const handleClickOutside = (e: MouseEvent) => {
  if (!myElement.current?.contains(e.target)) {
    // do something
  }
};

TypeScript Playground

  • Related