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 SearchScreen
component: 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} />}
</>
)
}