React JS ref (useRef): contains is not a function

7.9k views Asked by At

I am working on a React JS project. I am using hooks and functional components. I am using ref. I am using useRef because I am in a functional component. But the contains function is not working on ref. Following is my code.

const App = () => {
    let refDialog = useRef(null);
    
    document.addEventListener('click', (e) => {
      if (refDialog && refDialog.contains(e.target)) {
         // I will do something here
      }
    })
   
    return (
      <div>
        <Dialog ref={ ref => refDialog = ref}>My dialog content</Dialog>
     </div>
    )
}

As you can see in my component, in the event listener, it is throwing the following error.

Uncaught TypeError: refDialog.contains is not a function

What is wrong with my code?

4

There are 4 answers

2
kind user On

Firstly remove typo - useRf -> useRef. Secondly - refDialog is a read-only variable, you can't overwrite it - ref={refDialog} is enough (without ref function). Finally - refDialog will hold an object with current field. So the final condition will be:

if (refDialog.current && refDialog.current.contains(e.target))
0
Yuan-Hao Chiang On

Dialog may not be handling refs internally. In such case, you can wrap it inside a div (it will not have the same exact effect, but it probably just work fine nonetheless):

const App = () => {
  const refDialog = useRef(null);
    
  useEffect(() => {
    document.addEventListener('click', handleClick);
    
    return () => document.removeEventListener('click', handleClick);
  }, []);

  const handleClick = e => {
    if (refDialog.current?.contains(e.target)) {
      // I will do something here
    }
  }
   
  return (
    <div ref={refDialog}>
       <Dialog}>My dialog content</Dialog>
   </div>
  );
}

Also, notice the use of useEffect, the way your code is defined, with every render, a new event listener is being created and not cleared. You probably only want to do this once when the component is created.

If Dialog is a component you defined, you need to make sure the ref is being passed correctly using a forwardRef.

const Dialog = React.forwardRef((props, ref) => (
  <div ref={ref} className="Dialog">{ props.children}</div
));
0
Ashwin Saxena On

The below code should work:

import React from "react";
const App2 = () => {
  let refDialog = React.useRef(null);

  document.addEventListener("click", (e) => {
    if (
      refDialog &&
      refDialog.current &&
      refDialog.current.contains(e.target)
    ) {
      console.log("xx");
    }
  });

  return (
    <div>
      <div ref={refDialog}>My dialog content</div>
    </div>
  );
};

You had a typo in useRef. You need to check if current property exists on refDialog. Unlike its class counterpart, useRef hook doesn't return DOM node directly.

0
Veno On

Here is the working version of your problem,

import React, { useEffect } from 'react';

// YOUR OTHER IMPORT GOES HERE

const App = () => {
    const dialogRef = React.useRef(null);

    useEffect(() => {
        document.addEventListener("click", handleClickElement, false);
        // THIS WILL REMOVE THE LISTENER ON UNMOUNT
        return () => {
            document.removeEventListener("click", handleClickElement, false);
        };
    }, []);

    const handleClickElement = event => {
        if (dialogRef.current && dialogRef.current.contains(event.target)) {
            // YOUR LOGIC
            console.log("Am Clicked!")
        }
    };
    return (
        <div>
            <Dialog ref={dialogRef}>My dialog content</Dialog>
        </div>
    )
}