I am trying to implement the product list & search feature with debounce, but SearchProducts
component's state search
is empty after the dispatch action in the ProductList
component. Can someone please suggest what is wrong with the below code?
ProductList
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useState
} from "react";
import { fetchData } from "../../services/productService";
import { useDispatch, useSelector } from "react-redux";
import {
fetchProductInfo,
} from "../../store/features/productsSlice";
import ToastContext from "../../context/ToastContext";
import "bootstrap-icons/font/bootstrap-icons.css";
import { useNavigate } from "react-router-dom";
import { FETCH_PRODUCTS_END_URL } from "../../utils/constant";
import SearchProducts from "./SearchProducts";
import SearchBox from "./SearchBox";
const ProductList = () => {
const navigate = useNavigate();
const toastCtx = useContext(ToastContext);
const dispatch = useDispatch();
const { products, loading, error, status } = useSelector(
(state) => state.products
);
const [keySearch, SearchForm] = useForm('');
useEffect(() => {
dispatch(fetchProducts());
}, []);
if (loading) {
return <h2>Products is loading ....</h2>;
}
if (error) {
return <h2>{error}</h2>;
}
return (
<div className="container mt-2">
<h1 className="text-center mt-4">Products List</h1>
<SearchProducts />
<table className="table mt-4">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Category</th>
<th scope="col">Price</th>
</tr>
</thead>
<tbody>
{products.length > 0 &&
products.map((product) => {
return (
<tr key={product._id}>
<td className="col">{product.name}</td>
<td className="col">{product.category}</td>
<td className="col">{product.price}</td>
</tr>
);
})}
{products.length === 0 && (
<tr>
<td className="text-center" colSpan="4">
<strong>No products available !!</strong>
</td>
</tr>
)}
</tbody>
</table>
</div>
);
};
export default ProductList;
Search Product
import React, { useEffect, useState, useCallback } from "react";
import { useDispatch } from "react-redux";
import { searchProducts } from "../../store/features/productsSlice";
const SearchProducts = (props) => {
const [search, setSearch] = useState("");
const dispatch = useDispatch();
let seacrhClick = useCallback(() => {
if(search.length > 0){
dispatch(searchProducts(search));
}
}, [search]);
useEffect(() => {
let timerID = setTimeout(() => {
seacrhClick();
}, 1000);
return () => {
clearTimeout(timerID);
};
}, [search]);
return (
<div className="container">
<input
key={`search_${search}`}
className="w-50 form-control"
type="text"
name="search"
autoFocus
value={search || ''}
placeholder="Search Products"
onChange={(e) => {
setSearch(e.target.value);
}}
/>
</div>
);
};
export default SearchProducts;
searchProduct action
export const searchProducts = (key) => {
return async (dispatch, getState) => {
try {
dispatch(setStatus(PRODUCTS_STATUS.REQUESTED));
dispatch(setLoading(true));
const response = await fetchData(`${PRODUCTS_SEARCH_URL}/${key}`);
dispatch(setStatus(PRODUCTS_STATUS.SUCCESS));
dispatch(setLoading(false));
dispatch(setProducts(response?.data));
} catch (err) {
dispatch(
setError(
err?.response?.data?.message
? err?.response?.data?.message
: err.message
)
);
dispatch(setLoading(false));
dispatch(setStatus(PRODUCTS_STATUS.FAILED));
}
}
}
When you are actively searching, i.e. the searchProducts
action is dispatched, the ProductList
component conditionally renders some alternative UI. This means that SearchProducts
is unmounted. When the loading
and error
states are both falsey again after searching, SearchProducts
is re-mounted, with default initial state, again.
Refactor the rendered UI to maintain SearchProducts
being mounted.
Example refactor:
const ProductList = () => {
const dispatch = useDispatch();
const { products, loading, error, status } = useSelector(
(state) => state.products
);
useEffect(() => {
dispatch(fetchProducts());
}, []);
return (
<div className="container mt-2">
<h1 className="text-center mt-4">Products List</h1>
<SearchProducts />
{loading
? <h2>Products is loading ....</h2>
: (
<>
{error && <h2>{error}</h2>}
<table className="table mt-4">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Category</th>
<th scope="col">Price</th>
</tr>
</thead>
<tbody>
{products.length
? products.map((product) => (
<tr key={product._id}>
<td className="col">{product.name}</td>
<td className="col">{product.category}</td>
<td className="col">{product.price}</td>
</tr>
))
: (
<tr>
<td className="text-center" colSpan="4">
<strong>No products available !!</strong>
</td>
</tr>
)
}
</tbody>
</table>
</>
)
}
</div>
);
};