Home > Back-end >  how to cleanup the useEffect function in react js after removing component
how to cleanup the useEffect function in react js after removing component

Time:12-24

I am getting this error while using useRef and useEffect in react js. **how can i cleanup the useEffect in React js this is main topic of this all question ** Dropdown.js:9

   Uncaught TypeError: Cannot read properties of null (reading 'contains')
at HTMLDocument.bodydroptoggler (Dropdown.js:9)

here is screenshot: enter image description here

I am getting this error when i click on the button named as "drop toggler"

here is code of app.js

import React, { useState } from "react";
import Dropdown from "./components/Dropdown";

const options = [
    {
        label: "red color is selected",
        value: "red",
    },
    {
        label: "blue color is selected",
        value: "blue",
    },
    {
        label: "green color is seleted",
        value: "green",
    },
];

const App = () => {
    const [dropactive, setDropactive] = useState(true);
    return (
        <div className="container ui">
            <button
                className="button ui"
                onClick={() => setDropactive(!dropactive)}
            >
                drop toggler
            </button>
            {dropactive ? <Dropdown options={options} /> : null}
        </div>
    );
};

export default App;

and here is code of dropdown.js

import React, { useState, useRef, useEffect } from "react";

const Dropdown = ({ options }) => {
    const [selected, setSelected] = useState(options[0]);
    const [open, setOpen] = useState(false);
    const ref = useRef();
    useEffect(() => {
        const bodydroptoggler = (event) => {
            if (ref.current.contains(event.target)) {
                return;
            }
            setOpen(false);
        };
        document.addEventListener("click", bodydroptoggler);
        return () => {
            document.removeEventListener("click", bodydroptoggler);
            console.log("work");
        };
    }, []);
    const RenderedOptions = options.map((option, index) => {
        if (selected.value === option.value) {
            return null;
        } else {
            return (
                <div
                    className="item"
                    key={index}
                    onClick={() => {
                        setSelected(option);
                    }}
                >
                    {option.label}
                </div>
            );
        }
    });

    return (
        <div ref={ref} className="ui form">
            <div className="field">
                <label className="text label">Select from here:</label>
                <div
                    className={`ui selection dropdown ${
                        open ? "active visible" : ""
                    }`}
                    onClick={() => setOpen(!open)}
                >
                    <i className="dropdown icon"></i>
                    <div className="text">{selected.label}</div>
                    <div className={`menu ${open ? "visible transition" : ""}`}>
                        {RenderedOptions}
                    </div>
                </div>
            </div>
        </div>
    );
};

export default Dropdown;

here is what i want to perform i just want to hide that form by clicking on the button.

how you can run this project

  1. just create a react app
  2. put code of app.js to app.js of your project
  3. dropdown.js inside the component folder

i hope this all detail will help you i you need anything more just commnet down thanks in advance

CodePudding user response:

It's complicated. Your code generally looks good so it took me a minute to understand why. But here's the why - the Dropdown component unmounts before the cleanup from the effect is run. So the click event still finds the handler, this time with a null reference for the ref (because the ref gets updated immediately).

Your code is correct, idomatic React - but this is an edge case that needs deeper understanding.

As the other answerer already mentioned, just add an optional check. But I thought you might like to know why.

CodePudding user response:

Have you tried using optional chaining since ref.current might sometimes be undefined?

if (ref.current?.contains(event.target))

Here's a codesandbox link with the fix.

Also some additional context from React Ref docs on why sometimes the ref might be null

React will assign the current property with the DOM element when the component mounts, and assign it back to null when it unmounts.

  • Related