Search code examples
react-native-web

Detect click outside of component with React Native Web?


Im using React Native Web. I need to detect when a user clicks outside of a component. This is only loaded on web so it doesn't have to work on native.

Ive been trying to use a version of this hook: https://usehooks.com/useOnClickOutside/

In my component:

useOnClickOutside(ref, () => setIsOpen(false));

In the hook:

function useOnClickOutside(ref, handler) {
  React.useEffect(
    () => {
      const listener = event => {
        // Do nothing if clicking ref's element or descendent elements
        if (!ref.current || ref.current.contains(event.target)) {
           return;
        }
        handler(event);
      };

      document.addEventListener("mousedown", listener);
      document.addEventListener("touchstart", listener);

      return () => {
        document.removeEventListener("mousedown", listener);
        document.removeEventListener("touchstart", listener);
      };
    },
    [ref, handler],
  );
}

Which gives me an error:

TypeError: ref.current.contains is not a function

If I log ref.current I can see that there is no contains method. Does this exist for React Native Web?


Solution

  • NOTE: this is only for react-native-web

    here is demo: https://snack.expo.io/@nomi9995/5b60ae

    <View> is custom class-based React component so it returns React component instance in ref. And it does not have any DOM-specific methods like .contains

    you can give unique className to View and access dom element by document.getElementsByClassName()[0]

    import React, { useState, useEffect, useRef } from "react";
    import { StyleSheet, Text, View, Button } from "react-native";
    
    function useOnClickOutside(ref, handler) {
      useEffect(() => {
        const listener = (event) => {
          if (!ref.current || document.getElementsByClassName("uniqueClassName")[0].contains(event.target)) {
            return;
          }
          handler(event);
        };
    
        document.addEventListener("mousedown", listener);
        document.addEventListener("touchstart", listener);
    
        return () => {
          document.removeEventListener("mousedown", listener);
          document.removeEventListener("touchstart", listener);
        };
      }, [ref, handler]);
    }
    
    export default function App() {
      const ref = useRef();
    
      const [isModalOpen, setModalOpen] = useState(false);
    
      useOnClickOutside(ref, () => setModalOpen(false));
    
      return (
        <View style={styles.container}>
          {isModalOpen ? (
            <View ref={ref} style={styles.modalBox} className="uniqueClassName">
              <Text style={{ color: "#FFFFFF" }}>this is modalBox</Text>
            </View>
          ) : (
            <Button onPress={() => setModalOpen(true)} title="Open Modal" />
          )}
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: "red",
        alignItems: "center",
        justifyContent: "center",
      },
      modalBox: {
        backgroundColor: "blue",
        height: 200,
        width: 200,
        justifyContent: "center",
        alignItems: "center",
      },
    });
    
    

    enter image description here