html - 试图在窗口上添加滚动事件侦听器,但得到 Uncaught TypeError: Cannot read property 'classList' of null
问题描述
我正在尝试将底部阴影添加到 react js 中滚动时的搜索栏。在我进入应用程序的第二页之前,它运行良好。
当我试图进入第二页时,它显示
未捕获的类型错误:无法读取 null 的属性“classList”
不工作的代码:
import React, { useEffect } from 'react';
import { AiOutlineSearch } from "react-icons/ai";
const SearchBar = ({ totalPrograms, programs, setPrograms }) => {
const handleScroll = () => {
if(window.scrollY) {
document.getElementById('sb-header').classList.add('h-shadow');
}
else {
document.getElementById('sb-header').classList.remove('h-shadow');
}
}
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {window.removeEventListener('scroll', handleScroll)};
},[]);
const handleSubmit = (event) => {
event.preventDefault();
const searchProgramName = document.getElementById('search').value;
if(searchProgramName) {
setPrograms(
programs.filter(program =>
program.name.toLowerCase().includes(searchProgramName)
)
);
}
else {
handleClick();
}
}
const handleClick = () => {
const allPhase = document.getElementsByName('phase');
const checkedPhaseValue = [];
allPhase.forEach(phase => {
if(phase.checked) {
checkedPhaseValue.push(phase.value);
}
});
setPrograms(checkedPhaseValue.length ?
totalPrograms.filter(
program => checkedPhaseValue.includes(program.phase.toLowerCase())
)
: totalPrograms
);
}
return (
<header id='sb-header' className="container header-sb">
<form className="container container-center" onSubmit={handleSubmit}>
<div className="type-search">
<AiOutlineSearch className="icon"/>
<input id="search" type="search" placeholder="search by program name"/>
</div>
<div className="checkbox-container">
<div className="d-inline">
<input type="checkbox" onClick={handleClick} name="phase" id="open_application" value="application_open"/>
<label htmlFor="open_application">Open Application</label>
</div>
<div className="d-inline">
<input type="checkbox" onClick={handleClick} name="phase" id="application_in_review" value="application_review"/>
<label htmlFor="application_in_review">Application in Review</label>
</div>
<div className="d-inline">
<input type="checkbox" onClick={handleClick} name="phase" id="in_progress" value="in_progress"/>
<label htmlFor="in_progress">in Progress</label>
</div>
<div className="d-inline">
<input type="checkbox" onClick={handleClick} name="phase" id="completed" value="completed"/>
<label htmlFor="completed">Completed</label>
</div>
</div>
</form>
</header>
)
}
export default SearchBar;
在第二页上,我将单击“详细信息”按钮。代码:
import React from 'react';
import { Link } from 'react-router-dom';
import { FaAngleDoubleRight } from 'react-icons/fa';
import * as ROUTES from '../Constants/routes';
const ProgramCards = ({ program }) => {
return (
<div className="card">
<div className="card-header">
<h1 className="p-name">{program.name}</h1>
</div>
<div className="card-body">
<h3 className="p-category">{program.category}</h3>
<small className="p-phase">{(program.phase).replace('_', ' ')}</small>
<p className="p-description">{program.shortDescription}</p>
</div>
<div>
<div className="date-duration">
<small className="p-date">Start Date: {program.startDate}</small>
<small className="p-duration">Duration: {program.duration}</small>
</div>
<Link to={ROUTES.PROGRAM_DETAILS}>
<button className="card-button">
Details <FaAngleDoubleRight className="icon angled-icon" />
</button>
</Link>
</div>
</div>
)
}
export default ProgramCards;
我的问题是,为什么它不起作用?
在此之后,我尝试了另一种方法。它运行良好。
工作代码:
import React, { useEffect, useState } from 'react';
import { AiOutlineSearch } from "react-icons/ai";
const SearchBar = ({ totalPrograms, programs, setPrograms }) => {
const [ scrolled, setScrolled ] = useState(false);
const handleScroll = () => {
if(window.scrollY > 10) {
setScrolled(true);
}
else {
setScrolled(false);
}
}
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {window.removeEventListener('scroll', handleScroll)};
},[]);
const handleSubmit = (event) => {
event.preventDefault();
const searchProgramName = document.getElementById('search').value;
if(searchProgramName) {
setPrograms(
programs.filter(program =>
program.name.toLowerCase().includes(searchProgramName)
)
);
}
else {
handleClick();
}
}
const handleClick = () => {
const allPhase = document.getElementsByName('phase');
const checkedPhaseValue = [];
allPhase.forEach(phase => {
if(phase.checked) {
checkedPhaseValue.push(phase.value);
}
});
setPrograms(checkedPhaseValue.length ?
totalPrograms.filter(
program => checkedPhaseValue.includes(program.phase.toLowerCase())
)
: totalPrograms
);
}
return (
<header className={`container header-sb ${scrolled && 'h-shadow'}`}>
<form className="container container-center" onSubmit={handleSubmit}>
<div className="type-search">
<AiOutlineSearch className="icon"/>
<input id="search" type="search" placeholder="search by program name"/>
</div>
<div className="checkbox-container">
<div className="d-inline">
<input type="checkbox" onClick={handleClick} name="phase" id="open_application" value="application_open"/>
<label htmlFor="open_application">Open Application</label>
</div>
<div className="d-inline">
<input type="checkbox" onClick={handleClick} name="phase" id="application_in_review" value="application_review"/>
<label htmlFor="application_in_review">Application in Review</label>
</div>
<div className="d-inline">
<input type="checkbox" onClick={handleClick} name="phase" id="in_progress" value="in_progress"/>
<label htmlFor="in_progress">in Progress</label>
</div>
<div className="d-inline">
<input type="checkbox" onClick={handleClick} name="phase" id="completed" value="completed"/>
<label htmlFor="completed">Completed</label>
</div>
</div>
</form>
</header>
)
}
export default SearchBar;
解决方案
您的第二种方法是更好的反应方式。通常不鼓励自己查询由 react 管理的 DOM。如果您需要 DOM 节点,请使用 ref。
第一个示例不起作用,因为handleScroll
每次组件重新渲染时都会重新创建。因此,删除监听器不会删除原始监听器,因为引用的函数handleScroll
已更改。
因此,当您的组件卸载时,侦听器不会被正确删除,但 react 会删除 DOM 节点。下次滚动时仍会调用处理程序,但您尝试查询的节点不再存在。
您必须在其中创建侦听器,useEffect
以便removeEventListener
引用正确的函数:
useEffect(() => {
const handleScroll = () => setScrolled(window.scrollY > 10);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
},[]);
或者,您可以使用useRef
创建对侦听器函数的稳定引用。
推荐阅读
- php - Docker-compose:使用 localhost 和 mysql 作为主机名连接到数据库
- python - Julia - Python lmfit 的等价物
- datatables - yadcf 错误:无法在 addRangeDateFilter 处读取 null 的属性“oFeatures”
- java - 我的可观察列表为所有对象存储相同的值
- swift - 如何在 Swift 中创建 15 位长度的随机字符串?
- r - 如何使用 !!与 R 中的 replace_na 一起使用
- google-apps-script - 如何使用 Apps 脚本将来自 Gmail 的数据附加到 Google 表格的新列中?
- ios - 如何索引子数组以保持原始数组的顺序
- vuetify.js - Vuetify 更改页面显示在 Vuetify-2 数据表中
- git-submodules - 使用特定的提交号添加 git 子模块