javascriptreactjsreduxreact-reduxjsx

Why component state is empty after dispatch redux action?


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));
    }
  }
}

enter image description here


Solution

  • 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>
      );
    };