我有两个子组件:
function (): React.JSX.Element{
//Some code//
return (
<>
<div></div>
...
<Filter someProps={props}/>
...
</>
)
}
案例 2:
我想在其他 Header 的子组件中重用 Filter,然后渲染给定的子组件,如下所示:
function(): React.JSX.Element {
function LocalChildWithFilter(): React.JSX.Element {
//some code//
return (
<>
<div></div>
...
<LocalChildWithFilter someProps={props}/>
</>
)
};
}
问题是当我使用情况2时,
但是,当我使用案例1时,一切都工作正常类别和搜索的效果都被触发,...
type MyHeaderProps = {
category: string,
changeCategory: Dispatch<string>,
searchValue: string,
changeSearchValue: Dispatch<string>,
};
export default function Header({ category, changeCategory, changeSearchValue, searchValue} : MyHeaderProps): ReactJsxElm {
function LogoElm({mainColor} : {mainColor: string}): ReactJsxElm {
return (
<a href="#" className={headerIsFixed ? "logo-on-fixed" : "logo-on-default"}>
<LogoIcon mainColor={mainColor}/>
</a>
)
};
function Infos(): ReactJsxElm {
return <ul className={headerIsFixed ? "logo-on-fixed" : "logo-on-default"}>
<li><a href="#aboutus">About Us</a></li>
<li><a href="#contacts">Contacts</a></li>
<li><button><img src="https://avatars.githubusercontent.com/u/71090230?s=400&u=a0e3cf64f7329d3bbad75547e25b67724e0d10c7&v=4" alt="account picture" /></button></li>
</ul>
};
function DefaultHeaderContent(): ReactJsxElm {
return (
<div className="header_default">
<LogoElm mainColor={appColors["extra-light-color"]}/>
/*Here is the filter*/
<Filter category={category} changeCategory={changeCategory} changeSearchValue={changeSearchValue} />
<Infos />
</div>
)
}
return (
//Scenario of case 1: Filter directly on return body
// Works fine
<header className={ !headerIsFixed ? "header--not-fixed" : "header--fixed" }>
<div className="header_default">
<LogoElm mainColor={appColors["extra-light-color"]}/>
<Filter category={category} changeCategory={changeCategory} changeSearchValue={changeSearchValue} />
<Infos />
</div>
</header>
//Scenario of case 2: Filter is inside another component
//Effect of filter's children won't trigger
<header className={ !headerIsFixed ? "header--not-fixed" : "header--fixed" }>
<DefaultHeaderContent /> // Filter is inside here
</header>
)
}
由于问题似乎来自
import { Dispatch, useState, useEffect, useMemo, useRef } from "react";
import "../styles/Filter.css"
type ReactJsxElm = React.JSX.Element;
type MyCategoriesProps = {
selectedCategory: MyFilterProps["category"],
changeCategory: MyFilterProps['changeCategory']
}
function Categories({selectedCategory, changeCategory}: MyCategoriesProps): ReactJsxElm {
const [dropdownHidden, setDropdownHidden] = useState(true);
const [focusCounter, setFocusCounter] = useState(0);
const query = matchMedia("(max-width: 330px)");
const [smallFilter, setSmallFilter] = useState(query.matches);
const dropdownIcon = document.querySelector<HTMLSpanElement>(".category-component_dropdown-btn_icon");
const menuWrapper = document.querySelector<HTMLDivElement>(".categories-component_menu-wrapper");
const dropDownBtn = document.querySelector<HTMLButtonElement>('.category-component_dropdown-btn');
useEffect(() => {
dropDownBtn?.addEventListener("focus", handleDropDownFocus);
function handleDropDownFocus(): void {
setDropdownHidden(false);
};
window.addEventListener("mousedown", handleClickedAway);
function handleClickedAway(e: Event): void {
const clickedTarget = e.target as HTMLElement;
const classList = clickedTarget.classList;
if(classList.contains("category-menu_list_title")) {
changeCategory(clickedTarget.innerHTML);
};
if(!classList.contains("refocus-dropdown")){
setDropdownHidden(true);
setFocusCounter(0)
};
if(classList.contains("refocus-dropdown")) {
setDropdownHidden(false);
setFocusCounter(focusCounter + 1);
}
};
if(focusCounter % 2 === 0) {
setDropdownHidden(true)
} else {
setDropdownHidden(false)
}
return () => {
window.removeEventListener("mousedown", handleClickedAway);
dropDownBtn?.removeEventListener("focus", handleDropDownFocus);
}
}, [dropdownHidden, focusCounter, dropDownBtn, changeCategory]);
switch(dropdownHidden) {
case true:
dropdownIcon?.classList.remove("category-component_dropdown-btn_icon--flip");
menuWrapper?.classList.remove("categories-component_menu-wrapper--show");
break;
case false:
dropdownIcon?.classList.add("category-component_dropdown-btn_icon--flip");
menuWrapper?.classList.add("categories-component_menu-wrapper--show");
break;
}
const categoriesNames: string[] = "backgrounds, fashion, nature, science, education, feelings, health, people, religion, places, animals, industry, computer, food, sports, transportation, travel, buildings, business, music".split(", ");
const categoriesList: ReactJsxElm[] = categoriesNames.map((category,) => {
category = category.charAt(0).toLocaleUpperCase() + category.slice(1);
return <li
key={category}>
<button className="button category-menu_list_title refocus-dropdown">
{category}
</button>
</li>
});
// Effect for switching the state of smallFilter
useEffect(() => {
query.addEventListener("change", handleWidthChange);
function handleWidthChange(event: MediaQueryListEvent): void {
event.matches ? setSmallFilter(true) : setSmallFilter(false);
}
return () => {
query.removeEventListener("change", handleWidthChange);
}
}, [query]);
let buttonContent: ReactJsxElm = <div className="dropdown-btn_big refocus-dropdown">
<p className="category-component_dropdown-btn_title refocus-dropdown">{selectedCategory}</p>
<span className="category-component_dropdown-btn_icon refocus-dropdown">
<img src="src/assets/chevron-down-solid.svg" alt="drop down icon" className="refocus-dropdown"/>
</span>
</div>
if(smallFilter) {
buttonContent = <div className="dropdown-btn_small refocus-dropdown">
<img src="src/assets/filter.svg" alt="filter icon" className="refocus-dropdown" />
</div>
}
return(
<div className="categories-component refocus-dropdown">
<button className="button category-component_dropdown-btn refocus-dropdown">
{buttonContent}
</button>
<div className="categories-component_menu-wrapper refocus-dropdown">
<div className="categories-component_menu refocus-dropdown">
<ul className="refocus-dropdown">
<button className="button category-menu_list_title refocus-dropdown">
All Images
</button>
{categoriesList}
</ul>
</div>
</div>
</div>
)
};
type MySearcProps = {
category: MyFilterProps["category"],
changeSearchValue: MyFilterProps["changeSearchValue"],
};
function Search({category, changeSearchValue, }: MySearcProps): ReactJsxElm {
const [searchBarIsFocused, setSearchBarIsFocused] = useState(false);
const [clearSearchBtnFocused, setClearSearchBtnFocused] = useState(false);
const description: string = `Search for ${category} images`;
const searchBar = document.querySelector<HTMLInputElement>("#searchbar");
const searchBarRef = useRef(document.querySelector<HTMLInputElement>("#searchbar"));
const clearSearchBtn = document.querySelector<HTMLButtonElement>(".search-component_X-btn");
const searchBarIcon = document.querySelector<HTMLImageElement>(".search-component_search-icon")
/* HERE IT IS */
// Functionalities for hiding and showint the
// search icon or clear button;
useEffect(() => {
function handleSearchBarFocused(): void {
setSearchBarFocuseCounter( searchBarFocuseCounter + 1);
setSearchBarIsFocused(true);
};
searchBar?.addEventListener("focus", handleSearchBarFocused);
function handleSearchBarBlured(): void {
setSearchBarFocuseCounter( searchBarFocuseCounter + 1);
// setSearchBarIsFocused(false);
};
searchBar?.addEventListener("blur", handleSearchBarBlured);
window.addEventListener("mousedown", handleMouseDown);
function handleMouseDown(event: Event): void {
if(clearSearchBtn){
const clickedTarget = event.target as HTMLElement;
if(clearSearchBtn === clickedTarget) {
setClearSearchBtnFocused(true);
if(searchBar) {
searchBar.value = "";
changeSearchValue("")
}
} else {
setClearSearchBtnFocused(false);
}
}
};
return () => {
searchBar?.removeEventListener('focus', handleSearchBarFocused);
searchBar?.removeEventListener("blur", handleSearchBarBlured);
window.removeEventListener("mousedown", handleMouseDown);
}
}, [changeSearchValue, clearSearchBtn, searchBar] );
// Hiding Or Showing search icon and clear button accordingly
useMemo(() => {
if(!searchBarIsFocused) {
searchBarIcon?.classList.remove("hide");
clearSearchBtn?.classList.add("hide");
};
if(searchBarIsFocused) {
clearSearchBtn?.classList.remove("hide");
searchBarIcon?.classList.add("hide");
};
if(searchBar?.value.length && searchBar?.value.length > 0) {
clearSearchBtn?.classList.remove("hide");
searchBarIcon?.classList.add("hide");
};
}, [clearSearchBtn?.classList, searchBar?.value.length, searchBarIcon?.classList, searchBarIsFocused])
useEffect(() => {
searchBar?.addEventListener("keyup", handleUserKeyUped);
function handleUserKeyUped(e: Event): void {
const target = e.target as HTMLInputElement
const delay = 1500;
let sendTextTimeout: number | undefined = undefined;
if (sendTextTimeout) {
clearTimeout(sendTextTimeout);
}
sendTextTimeout = window.setTimeout(() => {
changeSearchValue(target.value);
}, delay);
};
}, [changeSearchValue, searchBar])
return(
<div className="search-component">
<input type="text" className="search-component_bar" id="searchbar" placeholder={description} />
<img className="search-component_search-icon" src="src/assets/magnifying-glass-solid 3.svg" alt="" />
<button className="search-component_X-btn" tabIndex={1}>X</button>
</div>
)
};
export type MyFilterProps = {
category: string,
changeCategory: Dispatch<string>,
changeSearchValue: Dispatch<string>,
}
export default function Filter({category, changeCategory, changeSearchValue}: MyFilterProps): ReactJsxElm {
return (
<div className="filter">
<Categories selectedCategory={category} changeCategory={changeCategory} />
<Search category={category} changeSearchValue={changeSearchValue}/>
</div>
)
}
import { Dispatch, useState, useEffect, useMemo, useRef } from "react";
import "../styles/Filter.css"
type ReactJsxElm = React.JSX.Element;
type MyCategoriesProps = {
selectedCategory: MyFilterProps["category"],
changeCategory: MyFilterProps['changeCategory']
}
function Categories({selectedCategory, changeCategory}: MyCategoriesProps): ReactJsxElm {
//Nothing has changed
}
type MySearcProps = {
category: MyFilterProps["category"],
changeSearchValue: MyFilterProps["changeSearchValue"],
};
function Search({category, changeSearchValue, }: MySearcProps): ReactJsxElm {
const [searchBarIsFocused, setSearchBarIsFocused] = useState(false);
const [clearSearchBtnFocused, setClearSearchBtnFocused] = useState(false);
const description: string = `Search for ${category} images`;
const searchBar = document.querySelector<HTMLInputElement>("#searchbar");
const searchBarRef = useRef(document.querySelector<HTMLInputElement>("#searchbar"));
const clearSearchBtn = document.querySelector<HTMLButtonElement>(".search-component_X-btn");
const searchBarIcon = document.querySelector<HTMLImageElement>(".search-component_search-icon")
useEffect(() => {
function handleSearchBarFocused(): void {
setSearchBarIsFocused(true);
};
searchBar?.addEventListener("focus", handleSearchBarFocused);
return () => {
searchBar?.removeEventListener('focus', handleSearchBarFocused);
}
}, [searchBar] );
useEffect(() => {
function handleSearchBarBlured(): void {
setSearchBarIsFocused(false);
};
searchBar?.addEventListener("blur", handleSearchBarBlured);
return () => {
searchBar?.removeEventListener("blur", handleSearchBarBlured);
}
}, [searchBar]);
useEffect(() => {
window.addEventListener("mousedown", handleMouseDown);
function handleMouseDown(event: Event): void {
if(clearSearchBtn){
const clickedTarget = event.target as HTMLElement;
if(clearSearchBtn === clickedTarget) {
setClearSearchBtnFocused(true);
if(searchBar) {
searchBar.value = "";
changeSearchValue("")
}
} else {
setClearSearchBtnFocused(false);
}
}
};
return () => {
window.removeEventListener("mousedown", handleMouseDown);
}
}, [changeSearchValue, clearSearchBtn, searchBar]);
// Hiding Or Showing search icon and clear button accordingly
useMemo(() => {
if(!searchBarIsFocused) {
searchBarIcon?.classList.remove("hide");
clearSearchBtn?.classList.add("hide");
};
if(searchBarIsFocused) {
clearSearchBtn?.classList.remove("hide");
searchBarIcon?.classList.add("hide");
};
if(searchBar?.value.length && searchBar?.value.length > 0) {
clearSearchBtn?.classList.remove("hide");
searchBarIcon?.classList.add("hide");
};
}, [clearSearchBtn?.classList, searchBar?.value.length, searchBarIcon?.classList, searchBarIsFocused])
useEffect(() => {
searchBar?.addEventListener("keyup", handleUserKeyUped);
function handleUserKeyUped(e: Event): void {
const target = e.target as HTMLInputElement
const delay = 1500;
let sendTextTimeout: number | undefined = undefined;
if (sendTextTimeout) {
clearTimeout(sendTextTimeout);
}
sendTextTimeout = window.setTimeout(() => {
changeSearchValue(target.value);
}, delay);
};
}, [changeSearchValue, searchBar])
return(
<div className="search-component">
<input type="text" className="search-component_bar" id="searchbar" placeholder={description} />
<img className="search-component_search-icon" src="src/assets/magnifying-glass-solid 3.svg" alt="" />
<button className="search-component_X-btn" tabIndex={1}>X</button>
</div>
)
};
export type MyFilterProps = {
category: string,
changeCategory: Dispatch<string>,
changeSearchValue: Dispatch<string>,
}
export default function Filter({category, changeCategory, changeSearchValue}: MyFilterProps): ReactJsxElm {
return (
<div className="filter">
<Categories selectedCategory={category} changeCategory={changeCategory} />
<Search category={category} changeSearchValue={changeSearchValue}/>
</div>
)
}
import { Dispatch, useState, useEffect, useMemo, useRef } from "react";
import "../styles/Filter.css"
type ReactJsxElm = React.JSX.Element;
type MyCategoriesProps = {
selectedCategory: MyFilterProps["category"],
changeCategory: MyFilterProps['changeCategory']
}
function Categories({selectedCategory, changeCategory}: MyCategoriesProps): ReactJsxElm {
//Nothing Has Changed
};
type MySearcProps = {
category: MyFilterProps["category"],
changeSearchValue: MyFilterProps["changeSearchValue"],
};
function Search({category, changeSearchValue, }: MySearcProps): ReactJsxElm {
const [searchBarFocuseCounter, setSearchBarFocuseCounter] = useState(0); //HERE IT IS
const [searchBarIsFocused, setSearchBarIsFocused] = useState(false);
const [clearSearchBtnFocused, setClearSearchBtnFocused] = useState(false);
// to increment The counter each focus or blur time
useMemo(() => {
searchBarFocuseCounter % 2 === 0 ? setSearchBarIsFocused(false) : setSearchBarIsFocused(true)
}, [searchBarFocuseCounter])
const description: string = `Search for ${category} images`;
const searchBar = document.querySelector<HTMLInputElement>("#searchbar");
const searchBarRef = useRef(document.querySelector<HTMLInputElement>("#searchbar"));
const clearSearchBtn = document.querySelector<HTMLButtonElement>(".search-component_X-btn");
const searchBarIcon = document.querySelector<HTMLImageElement>(".search-component_search-icon")
useEffect(() => {
function handleSearchBarFocused(): void {
setSearchBarFocuseCounter( searchBarFocuseCounter + 1);
};
searchBar?.addEventListener("focus", handleSearchBarFocused);
return () => {
searchBar?.removeEventListener('focus', handleSearchBarFocused);
}
}, [searchBar, searchBarFocuseCounter] );
useEffect(() => {
function handleSearchBarBlured(): void {
setSearchBarFocuseCounter( searchBarFocuseCounter + 1);
};
searchBar?.addEventListener("blur", handleSearchBarBlured);
return () => {
searchBar?.removeEventListener("blur", handleSearchBarBlured);
}
}, [searchBar, searchBarFocuseCounter]);
useEffect(() => {
window.addEventListener("mousedown", handleMouseDown);
function handleMouseDown(event: Event): void {
if(clearSearchBtn){
const clickedTarget = event.target as HTMLElement;
if(clearSearchBtn === clickedTarget) {
setClearSearchBtnFocused(true);
if(searchBar) {
searchBar.value = "";
changeSearchValue("")
}
} else {
setClearSearchBtnFocused(false);
}
}
};
return () => {
window.removeEventListener("mousedown", handleMouseDown);
}
}, [changeSearchValue, clearSearchBtn, searchBar]);
// Hiding Or Showing search icon and clear button accordingly
useMemo(() => {
if(!searchBarIsFocused) {
searchBarIcon?.classList.remove("hide");
clearSearchBtn?.classList.add("hide");
};
if(searchBarIsFocused) {
clearSearchBtn?.classList.remove("hide");
searchBarIcon?.classList.add("hide");
};
if(searchBar?.value.length && searchBar?.value.length > 0) {
clearSearchBtn?.classList.remove("hide");
searchBarIcon?.classList.add("hide");
};
}, [clearSearchBtn?.classList, searchBar?.value.length, searchBarIcon?.classList, searchBarIsFocused])
useEffect(() => {
searchBar?.addEventListener("keyup", handleUserKeyUped);
function handleUserKeyUped(e: Event): void {
const target = e.target as HTMLInputElement
const delay = 1500;
let sendTextTimeout: number | undefined = undefined;
if (sendTextTimeout) {
clearTimeout(sendTextTimeout);
}
sendTextTimeout = window.setTimeout(() => {
changeSearchValue(target.value);
}, delay);
};
}, [changeSearchValue, searchBar])
return(
<div className="search-component">
<input type="text" className="search-component_bar" id="searchbar" placeholder={description} />
<img className="search-component_search-icon" src="src/assets/magnifying-glass-solid 3.svg" alt="" />
<button className="search-component_X-btn" tabIndex={1}>X</button>
</div>
)
};
export type MyFilterProps = {
category: string,
changeCategory: Dispatch<string>,
changeSearchValue: Dispatch<string>,
}
export default function Filter({category, changeCategory, changeSearchValue}: MyFilterProps): ReactJsxElm {
return (
<div className="filter">
<Categories selectedCategory={category} changeCategory={changeCategory} />
<Search category={category} changeSearchValue={changeSearchValue}/>
</div>
)
}
我还尝试使用 useRef 而不是直接使用 DOM 访问 searchBar,但它不起作用
/*...*/
const searchBarRef = useRef(document.querySelector<HTMLInputElement>("#searchbar"));
/*...*/
useEffect(() => {
searchBarRef.current?.addEventListener("focus", handleSearchBarFocused);
/*..*/
如果您有任何想法或任何解决方案,请感谢您的回复🙏🙏🙏
我终于找到了问题和解决方案。经过一番调试,...
问题是我试图在另一个组件(父组件)中重用一个组件,即 Filter(有两个子组件)到 Header 的某个本地子组件中。尽管可以这样做(因为 Filter 的其他子级没有遇到任何问题),但 Filter 的子级具有一些本地状态和效果,一旦父级渲染,这些状态和效果就会被触发。因此,状态是双重触发的,并产生“无效”状态的效果(因为状态/效果是关于打开/关闭某些功能的)。
所以我做了什么: 我能够重用该组件,通过移动标头的本地子组件,重新使用过滤器本身,并在本地提供而不是使用过滤器的状态,我将它们移动到父标头中,并将它们用作标头中的道具当地的孩子(正在重用)。
所以代替这个:
function Header(): React.JSX.Element {
function LocalChildWithFilter(): React.JSX.Element {
//some code//
return <>
//Some Markup
</>
)
};
return
<>
<div></div>
...
<LocalChildWithFilter someProps={props}/>
...
<span></span>
</>
}
我做了这个
function LocalChildWithFilter(): React.JSX.Element {
//some code//
return <>
<div>Blahblabla</div>
</>
)
};
function Header(): React.JSX.Element {
//Code
return
<>
<div></div>
...
<LocalChildWithFilter someProps={props}/>
...
<span></span>
</>
}
<br><br>
我终于找到了问题和解决方案。经过一番调试,...
问题是我试图在另一个组件(父组件)中重用一个组件,即 Filter(有两个子组件)到 Header 的某个本地子组件中。尽管可以这样做(因为 Filter 的其他子级没有遇到任何问题),但 Filter 的子级具有一些本地状态和效果,一旦父级渲染,这些状态和效果就会被触发。因此,状态是双重触发的,并产生“无效”状态的效果(因为状态/效果是关于打开/关闭某些功能的)。
所以我做了什么: 我能够重用该组件,通过移动标头的本地子组件,重新使用过滤器本身,并在本地提供而不是使用过滤器的状态,我将它们移动到父标头中,并将它们用作标头中的道具当地的孩子(正在重用)。
所以代替这个:
function Header(): React.JSX.Element {
function LocalChildWithFilter(): React.JSX.Element {
//some code//
return <>
//Some Markup
</>
)
};
return
<>
<div></div>
...
<LocalChildWithFilter someProps={props}/>
...
<span></span>
</>
}
我做了这个
function LocalChildWithFilter(): React.JSX.Element {
//some code//
return <>
<div>Blahblabla</div>
</>
)
};
function Header(): React.JSX.Element {
//Code
return
<>
<div></div>
...
<LocalChildWithFilter someProps={props}/>
...
<span></span>
</>
}
<br><br>