Reputation: 977
I use a useEffect in my React code which works fine at first but writing a search engine using a useState called "search" when setting its new value through an input onChange my useEffect is executed automatically again although it is never waiting changing my useState to "search", this is my code
const [search, setSearch] = useState("");
useEffect(() => {
pages();
}, [limitPage, page, products]);
const pages = () => {
// eslint-disable-next-line
getProducts(limitPage, page, search);
getXlsProducts(100000, 1);
const totalPages = Math.ceil(totalDocs / limitPage);
const links = [];
for (let i = 1; i <= totalPages; i++) {
links[i] = i;
}
setNumbers(links);
setTotalDocs(products.products?.totalDocs);
setTotalPages(products.products?.totalPages);
setCurrentPage(products.products?.page);
setNextPage(products.products?.hasNextPage);
setPreviousPage(products.products?.hasPrevPage);
};
I need to use the button that calls the handleSearch() function in order to perform the search, but as soon as a change in the input automatically occurs, my useEffect is executed:
<div>
<input
className="border-1 border-2 border-solid rounded border-slate-300"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<button onClick={() => handleSearch()}>
<FiSearch />
</button>
</div>
Here is the function to use when clicking on the button but it is never used because as soon as search changes value through the input it no longer gets executed:
const handleSearch = () => {
setLimitPage(10);
setPage(1);
getProducts(limitPage, page, search);
};
How can I avoid the automatic firing of my useEffect so that I can use my search function through the button? thanks.
Upvotes: 0
Views: 264
Reputation: 5429
useEffect(() => {
pages();
}, [limitPage, page, products]);
Dependencies in React's hooks use shallow comparison.
As @ChrisG mentioned in the comment - "You have infinite loop". It's async loop because as you said, getProducts
function request products from the server. So when you get a list of products from the server even if they semantically equal as already set you still create a new array with new reference. So useEffect
triggered again because reference on products list has changed!
//🥇triggered useEffect by first run
// when we create component and call `pages()`
useEffect(() => {
pages();
}, [limitPage, page, products]);
const pages = () => {
//🥈Make a request to the server and set products list
// like setProducts(response.products) this cause
// create new array with new reference
getProducts(limitPage, page, search);
...
}
//🥉products === response.products // False because these are two different objects
// so we trigger useEffect again (returns to 🥈)
useEffect(() => {
pages();
}, [limitPage, page, products]);
Upvotes: 7
Reputation: 383
As far as I have understood , you want getProducts()
to be called on the first render
as well as whenever the state limitPage and page
changes and whenever Search
Button is pressed.
You already have handleSearch
function for calling getProducts()
that is triggering the API on the click of Search
button,
const handleSearch = () => {
setLimitPage(10);
setPage(1);
getProducts(limitPage, page, search);
};
In the handleSearch() as mentioned by @ChrisG in comment :- you need to take care of limitPage
and page
value as it will use the previous value here.
for calling API on first render
and whenever the states limitPage
and page
changes, remove products
dependency from useEffect
and modify pages() function with the required statements:-
useEffect(() => {
pages();
}, [limitPage, page]);
const pages = () => {
// eslint-disable-next-line
getProducts(limitPage, page, search);
getXlsProducts(100000, 1);
const totalPages = Math.ceil(totalDocs / limitPage);
const links = [];
for (let i = 1; i <= totalPages; i++) {
links[i] = i;
}
setNumbers(links);
};
I saw several states that you want to update on the change of products
state,
for that , you can have another useEffect
with products
dependency and a new method updating the required states:-
useEffect(() => {
updateStates();
}, [products]);
const updateStates = () => {
setTotalDocs(products.products?.totalDocs);
setTotalPages(products.products?.totalPages);
setCurrentPage(products.products?.page);
setNextPage(products.products?.hasNextPage);
setPreviousPage(products.products?.hasPrevPage);
};
Upvotes: 1
Reputation: 1861
The problem is the shallow comparison as stated by @maksimr.
An alternative is to use class based components with shouldComponentUpdate
:
shouldComponentUpdate(nextProps, nextState) {
// if no products in current state, update when there are products in
// future state, or else remain.
if(!this.state.products.length) {
return nextState.products.length;
}
// update if there's a product in current state whose id
// is not found in the next state's products
return this.state.products.some((prod)=>(
!nextState.products.find((p)=> (
prod.id == p.id
)
);
}
Upvotes: 0