I have a state which is used to render some components conditionally, I have the following structure:
const SearchScreen = () => {
const [isSearchBarFocused, setIsSearchBarFocused] = React.useState(false);
return (
<>
<SearchBar onFocus={setIsSearchBarFocused} />
{isSearchBarFocuse ? <SearchSuggestionList isSearchBarFocused={isSearchBarFocused} setIsSearchBarFocused={setIsSearchBarFocused} /> : null}
</>
)
}
As you can see in the code above, the state seter is shared by both components, when the <SearchBar /> is focused the state changes to true and when an option is selected from the <SearchSuggestionList /> the state change to false. The problem with this approach is that I need to run some API calls and other inner state updates in <SearchSuggestionList /> component but this is unmounted due to the conditional rendering then some processes are not reached to be carried out.
then how would I execute code that is inside a component that can be unmounted?
CodePudding user response:
At SearchScreencomponent: You need to add event onBlur for search-input, it will auto trigger when click outside search-input. And add two events onChange and value to get the enter value from search-input. Finally, don't check isSearchBarFocused variable outside SearchSuggestionList component.
export default function SearchScreen() {
const [isSearchBarFocused, setIsSearchBarFocused] = useState(false);
const [searchBarValue, setSearchBarValue] = useState("");
const handleChangeSearchValue = (e) => {
setSearchBarValue(e.target.value);
};
const handleBlurSearch = () => {
setIsSearchBarFocused(false);
};
const handleFocusSearch = () => {
setIsSearchBarFocused(true);
};
return (
<div className="App">
<div className="searchbar-wrapper">
<input
className="search-input"
type="text"
onFocus={handleFocusSearch}
onChange={handleChangeSearchValue}
value={searchBarValue}
onBlur={handleBlurSearch}
/>
<SearchSuggestionList
isSearchBarFocused={isSearchBarFocused}
searchBarValue={searchBarValue}
/>
</div>
</div>
);
}
At SearchSuggestionList component: You need to create isSearchBarFocused and searchBarValue for Props. The display atrribute will change none or block depend on isSearchBarFocused
.search-list {
display: none;
}
.search-list.active {
display: block;
}
and if searchBarValue changes value, useEffect will trigger and re-call API
import { useEffect, useState } from "react";
const MOCKUP_DATA = [
{ id: 1, value: "Clothes" },
{ id: 2, value: "Dress" },
{ id: 3, value: "T-shirt" }
];
const SearchSuggestionList = ({ searchBarValue, isSearchBarFocused }) => {
const [suggestionData, setSuggestionData] = useState([]);
useEffect(() => {
// useEffect will call API first time and re-call every time `searchBarValue` changed value.
console.log("re-call api");
setSuggestionData(MOCKUP_DATA);
}, [searchBarValue]);
const handleClick = (value) => {
console.log(value);
};
return (
<ul className={`search-list ${isSearchBarFocused ? "active" : ""}`}>
{suggestionData.map((item) => (
<li key={item.id} onm ouseDown={() => handleClick(item.value)}>
{item.value}
</li>
))}
</ul>
);
};
export default SearchSuggestionList;
Warning: If you use onClick to replace onMouseDown, it will not work. Because onBlur event of search-input will trigger before onClick event of the items in SearchSuggestionList
You can demo it at this link: https://codesandbox.io/s/inspiring-cori-m1bsv0?file=/src/App.jsx
CodePudding user response:
Please try to use && instead of ternary operator(?) like this:
const SearchScreen = () => {
const [isSearchBarFocused, setIsSearchBarFocused] = React.useState(false);
return (
<>
<SearchBar onFocus={setIsSearchBarFocused} />
{isSearchBarFocused && <SearchSuggestionList isSearchBarFocused={isSearchBarFocused} setIsSearchBarFocused={setIsSearchBarFocused} />}
</>
)
}
