How to create Search functionality in MERN Stack + Redux

How to create Search functionality in MERN Stack + Redux

Step 1: Create a controller and route for search functionality in your backend

//controller
const searchBlog = async (req, res) => {
  const searchTerm = req.query.q; // Assuming 'q' as the search parameter

  try {
    const results = await Blog.find({
      $or: [
        { title: { $regex: searchTerm, $options: 'i' } }, // Case-insensitive search for title
        { content: { $regex: searchTerm, $options: 'i' } }, // Case-insensitive search for content
      ],
    });

    res.json(results);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
}; 

//route
blogRouter.get("/search",blogControllers.searchBlog);

Step 2: As we are using Redux then move into your api file and make a request to the above created endpoint

import axios from "axios"

const API = axios.create({baseURL:"http://localhost:8800/"});
export const searchBlogs = (searchTerm) => API.get(`blogs/search?q=${searchTerm}`);

Step 3: Now create an action for this functionality

import * as api from "../api";
import {
  SEARCH_BLOGS
} from "../constants/actionTypes";

export const searchBlogs = (searchTerm) => async (dispatch) => {
  try {
    const { data } = await api.searchBlogs(searchTerm);
    dispatch({ type: SEARCH_BLOGS, payload: data });
  } catch (error) {
    console.log(error);
  }
};

Step 4: Create a reducer for this action

import { CREATE_BLOG, DELETE_BLOG, FETCH_BLOGS, FETCH_BLOG_ID, SEARCH_BLOGS, UPDATE_BLOG } from "../constants/actionTypes";

export default (blogs = [], action) => {
  switch (action.type) {
    case SEARCH_BLOGS:
      return action.payload; 
    default:
      return blogs;
  }
};

Step 5: Move into the component in which you want to include this search functionality

import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getBlogs, searchBlogs } from "../actions/blogs";
import { NavLink } from "react-router-dom";
import Loader from "./Loader"; // Import the Loader component

const Blogs = () => {
  const dispatch = useDispatch();
  const blogs = useSelector((state) => state.blogs);
  const [searchTerm, setSearchTerm] = useState("");
  const [isLoading, setIsLoading] = useState(true); // New state for loading indicator

  const handleSearch = () => {
    if (searchTerm.trim() !== "") {
      dispatch(searchBlogs(searchTerm));
    } else {
      dispatch(getBlogs());
    }
  };
  return (
    <div>
      <input
        type="text"
        placeholder="Search blogs"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <button onClick={handleSearch}>Search</button>

      {isLoading ? (
        <Loader /> // Display the loader while fetching blogs
      ) : (
        blogs.map((item, index) => {
          return (
            <div key={index}>
              <p>{item.content}</p>
            </div>
          );
        })
      )}
    </div>
  );
};

export default Blogs;

Loader Component

import React from 'react';
import './Loader.css'; // Make sure to create Loader.css file

const Loader = () => {
  return (
    <div className="loader-container">
      <div className="loader"></div>
    </div>
  );
};

export default Loader;
/* Loader.css */

.loader-container {
    display: flex;
    justify-content: center;
    align-items: center;
    /* height: 100vh; */
  }

  .loader {
    border: 4px solid rgba(0, 0, 0, 0.1);
    border-top: 4px solid #000;
    border-radius: 50%;
    width: 40px;
    height: 40px;
    animation: spin 1s linear infinite;
  }

  @keyframes spin {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }