skip to Main Content

I added a event listener to the document for a keydown event, and the event listener works, within the event listener I call a function, and even that function works perfectly, everything works, but I still get a:

Cannot read properties of undefined (reading 'classList')
TypeError: Cannot read properties of undefined (reading 'classList')
    at toggleSearch (http://localhost:3000/static/js/bundle.js:236:29)
    at HTMLDocument.<anonymous> (http://localhost:3000/static/js/bundle.js:293:7)

error, and if I close the error from the top left cross(x) icon, I can see that the function executed perfectly, but why I am getting the error if it works?

code:

import Logo from "./Logo";
import React, { useRef, useEffect, useState } from "react";
import useEventListener from "../hooks/useEventListerner";
import axios from "axios";
// import getResults from "../hooks/search";

function Header() {
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  const menuButton = useRef();
  const menu = useRef();
  const searchButton = useRef();
  const searchButton2 = useRef();
  const searchContainer = useRef();
  const cancelButton = useRef();
  const searchInput = useRef();
  const result = useRef();
  const url = "https://graphql.anilist.co";
  const searchString = `query ($query: String){ Page(page: 1, perPage: 10) { media(search: $query, type: MANGA, sort: SEARCH_MATCH) { id coverImage { medium } title { english romaji } averageScore startDate { year } isAdult status }}}`;
  const advance = useRef();
  // Scrollbar.initAll();
  console.log(result, typeof result, result.current, typeof result.current);
  function SearchCard(props) {
    if (props.isAdult === true) {
      var adultCont = "nsfw-cont";
      var adultImage = "nsfw";
    } else {
      // eslint-disable-next-line no-redeclare
      var adultCont = "";
      // eslint-disable-next-line no-redeclare
      var adultImage = "";
    }

    return (
      <a href={`/manga/${props.id}`} className="search-card">
        <div className={`search cover-container ${adultCont}`}>
          <img
            src={props.coverLink}
            alt=""
            className={`search cover ${adultImage}`}
            loading="lazy"
          />
        </div>
        <div className="search info">
          <h3 className="search title nsfw-title">
            <p>{props.title}</p>
          </h3>
          <span className="search subinfo">
            <p>★ {props.ratings}</p>
            <p className="bullet">•</p>
            <p>{props.release.year}</p>
            <p className="bullet">•</p>
            <p>{props.status}</p>
          </span>
        </div>
      </a>
    );
  }

  function toggleMenu() {
    menu.current.classList.toggle("visible-flex");
  }
  function toggleSearch() {
    searchContainer.current.classList.toggle("visible-flex");
    searchInput.current.focus();
  }

  var [advanceElem, setAdvanceElem] = useState(
    <p className="result-text">Search results will appear here</p>
  );

  function inputSearch() {
    const query = searchInput.current.value;
    if (query === "") {
      setAdvanceElem(
        <p className="result-text">Search results will appear here</p>
      );
    } else {
      setAdvanceElem(
        <a href={`/search/manga/${query}`} className="search-card advance">
          Advance Search for <b className="query">{query}</b>
        </a>
      );
    }
    axios
      .post(
        url,
        {
          query: searchString,
          variables: { query: query },
        },
        {
          headers: {
            "Content-Type": "application/json",
            Accept: "application/json",
          },
        }
      )
      .then((response) => {
        console.log(response);
      });
  }

  document.addEventListener("keydown", function (event) {
    if (event.ctrlKey && event.key === "k") {
      event.preventDefault();
      toggleSearch();
    }
  });

  useEventListener("keyup", inputSearch, searchInput);
  useEventListener("click", toggleSearch, searchButton);
  useEventListener("click", toggleSearch, searchButton2);
  useEventListener("click", toggleMenu, menuButton);
  useEventListener("click", toggleSearch, cancelButton);
  return (
    <header>
      <div className="header-inner wrapper">
        <Logo />
        <nav className="navbar">
          <button className="search-button" ref={searchButton}>
            <span className="search-icon nobdr">
              <svg
                className="fill-icon search-ic"
                viewBox="0 0 20 20"
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
              >
                <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
                <g
                  id="SVGRepo_tracerCarrier"
                  strokeLinecap="round"
                  stroklinejoin="round"
                ></g>
                <g id="SVGRepo_iconCarrier">
                  <path
                    fillRule="evenodd"
                    d="M4 9a5 5 0 1110 0A5 5 0 014 9zm5-7a7 7 0 104.2 12.6.999.999 0 00.093.107l3 3a1 1 0 001.414-1.414l-3-3a.999.999 0 00-.107-.093A7 7 0 009 2z"
                  ></path>
                </g>
              </svg>
            </span>
            <p className="placeholder">Search</p>
            <div className="keys">
              <kbd className="key">CTRL</kbd>
              <kbd className="key">K</kbd>
            </div>
          </button>
          <button
            className="nav-link wide"
            id="search-button"
            ref={searchButton2}
          >
            <span className="search-icon nobdr">
              <svg
                className="fill-icon search-ic"
                viewBox="0 0 20 20"
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
              >
                <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
                <g
                  id="SVGRepo_tracerCarrier"
                  strokeLinecap="round"
                  stroklinejoin="round"
                ></g>
                <g id="SVGRepo_iconCarrier">
                  <path
                    fillRule="evenodd"
                    d="M4 9a5 5 0 1110 0A5 5 0 014 9zm5-7a7 7 0 104.2 12.6.999.999 0 00.093.107l3 3a1 1 0 001.414-1.414l-3-3a.999.999 0 00-.107-.093A7 7 0 009 2z"
                  ></path>
                </g>
              </svg>
            </span>
          </button>
          <a href="/" className="nav-link wide">
            Library
          </a>
          <a href="/" className="nav-link smol">
            <svg
              className="fill-icon search-ic"
              viewBox="-8 0 60 60"
              xmlns="http://www.w3.org/2000/svg"
              fill="#000000"
            >
              <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
              <g
                id="SVGRepo_tracerCarrier"
                stroke-linecap="round"
                srokelinejoin="round"
              ></g>
              <g id="SVGRepo_iconCarrier">
                <path
                  className="cls-1"
                  d="M222,150a4,4,0,0,1-4-4V94a4,4,0,0,1,4-4h36l4,4H224a2,2,0,0,0-2,2v4a2,2,0,0,0,2,2,2,2,0,0,1,2,2v11.086a1,1,0,0,0,1.707.707l2.879-2.879a2,2,0,0,1,2.828,0l2.879,2.879a1,1,0,0,0,1.707-.707V104a2,2,0,0,1,2-2h22v48H222Z"
                  id="book"
                  transform="translate(-218 -90)"
                ></path>
              </g>
            </svg>
          </a>
          <button
            className="nav-link link-icon"
            id="menuButton"
            ref={menuButton}
          >
            <span className="dot a"></span>
            <span className="dot b"></span>
            <span className="dot c"></span>
          </button>
        </nav>
        <div className="menu" id="menu" ref={menu}>
          <a href="/" className="nav-link menu-link">
            Profile
          </a>
          <a href="/" className="nav-link menu-link">
            Discord
          </a>
          <a href="/" className="nav-link menu-link">
            Settings
          </a>
        </div>
        <div className="search-wrapper" ref={searchContainer}>
          <div className="search-container" id="search-container">
            <div className="searchbar">
              <span className="search-icon" id="search-icon">
                <svg
                  className="fill-icon search-ic"
                  viewBox="0 0 20 20"
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                >
                  <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
                  <g
                    id="SVGRepo_tracerCarrier"
                    strokeLinecap="round"
                    stroklinejoin="round"
                  ></g>
                  <g id="SVGRepo_iconCarrier">
                    <path
                      fillRule="evenodd"
                      d="M4 9a5 5 0 1110 0A5 5 0 014 9zm5-7a7 7 0 104.2 12.6.999.999 0 00.093.107l3 3a1 1 0 001.414-1.414l-3-3a.999.999 0 00-.107-.093A7 7 0 009 2z"
                    ></path>
                  </g>
                </svg>
              </span>
              <input
                type="text"
                id="search-input"
                placeholder="Search"
                ref={searchInput}
                autoComplete="off"
              />
              <button className="cancel" id="cancel" ref={cancelButton}>
                Cancel
              </button>
            </div>
            <div className="search-results" ref={result}>
              {advanceElem}
            </div>
          </div>
        </div>
      </div>
    </header>
  );
}

export default Header;
//useEventListener.js

import { useEffect } from "react";

function useEventListener(eventName, handler, element = window) {
  useEffect(() => {
    element.current.addEventListener(eventName, handler);

    return () => {
      element.current.removeEventListener(eventName, handler);
    };
  }, [eventName, handler, element]);
}

export default useEventListener;

2

Answers


  1. Chosen as BEST ANSWER
     const keyHandler = useCallback(
        (event) => {
          if (event.key.toLowerCase() === "k" && event.ctrlKey) {
            event.preventDefault();
            if (document.activeElement === searchInput.current) {
              searchInput.current.blur();
            } else {
              searchInput.current.focus();
            }
          } else if (event.key === "Escape") {
            searchInput.current.blur();
          }
        },
        [searchInput]
      );
    
      useEffect(() => {
        window.addEventListener("keydown", keyHandler);
        return () => window.removeEventListener("keydown", keyHandler);
      }, [searchInput, keyHandler]);
    

    It worked when I made it like this.


  2. Your error message implies that searchContainer.current is undefined, hence it does not contain a property classList.

    This is happening due to the fact that the component has not mounted (been rendered) for the first time.

    A ref in React will have a value of undefined, until the component mounts for the first time.

    To assure that your component has mounted and that searchContainer.current is actually the div that you intend to reference rather than undefined, you will need to place the event listener inside a useEffect hook with no dependencies, like this:

    useEffect(() => {
      document.addEventListener("keydown", function (event) {
        if (event.ctrlKey && event.key === "k") {
          event.preventDefault();
          toggleSearch()
        }
      });
    }), []);
    

    Also, adding a check so that the execution is not interrupted could be useful too.

    function toggleSearch() {
      if(!searchContainer.current) return;
      searchContainer.current.classList.toggle("visible-flex");
      searchInput.current.focus();
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search