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
It worked when I made it like this.
Your error message implies that
searchContainer.current
isundefined
, hence it does not contain a propertyclassList
.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 ofundefined
, 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 thanundefined
, you will need to place the event listener inside auseEffect
hook with no dependencies, like this:Also, adding a check so that the execution is not interrupted could be useful too.