How to do CRUD operations in Redux

How to do CRUD operations in Redux

⏰ Initial Setup for Redux

Step 1: Create reducer folder and inside index.js file combine all the reducers

import {combineReducers} from 'redux'
import blogs from './blogs'
const rootReducer = combineReducers({
 blogs
});

export default rootReducer;

Step 2: Create a store and using a Provider wrap it around our entire application

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import {thunk} from "redux-thunk"
import reducers from "./reducers"
const root = ReactDOM.createRoot(document.getElementById("root"));
//Creates a Redux store by combining reducers, applying middleware 
//(in this case, redux-thunk), and composing store enhancers using compose.
const store = createStore(reducers,compose(applyMiddleware(thunk)));
root.render(
  <React.StrictMode>
  <Provider store={store} >
  <App />
  </Provider>
  </React.StrictMode>
);

Step 3: Starting with our CRUD Operations

A . Fetching all Posts

Step 1: Fetch the api endpoint

import axios from "axios"

const API = axios.create({baseURL:"http://localhost:8800/"});

export const fetchPosts = () => API.get("/blogs")

Step 2: Create action using this endpoint

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

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

Step 3: Create a reducer for fetching all blogs Operation

import { FETCH_BLOGS } from "../constants/actionTypes";

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

Step 4: Now inside the component where you want to use this data

import React, { useEffect } from 'react'
import {useDispatch, useSelector} from 'react-redux'
import { getBlogs } from '../actions/blogs';
const Blogs = () => {
    const dispatch = useDispatch();
    const blogs = useSelector((state)=>state.blogs);
    console.log(blogs);    
    useEffect(()=>{
        dispatch(getBlogs());
    },[dispatch])
  return (
    <div>Blogs</div>
  )
}

export default Blogs

B. Creating a Blog

Step 1: Fetch the api endpoint

import axios from "axios"

const API = axios.create({baseURL:"http://localhost:8800/"});

export const createBlog = (newBlog) => API.post("/blogs",newBlog);

Step 2: Create action for this endpoint

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

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

Step 3: Create a reducer for this Operation

import { CREATE_BLOG} from "../constants/actionTypes";

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

Step 4: Inside the Form Component

import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { createBlog } from "../actions/blogs";
import {useNavigate} from 'react-router-dom'
const Write = () => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [image, setImage] = useState("");
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const handleCreatePost = async(e) => {
    e.preventDefault();
    try {
      const newBlog = {
        title,
        content,
        image,
      };
      await dispatch(createBlog(newBlog));
      navigate("/");
    } catch (error) {
      console.log(error);
    }
  };
  return (
    <>
      <form>
        <input
          onChange={(e) => setTitle(e.target.value)}
          placeholder="Enter your title"
        />
        <input
          onChange={(e) => setImage(e.target.value)}
          placeholder="Enter your image url"
        />
        <textarea
          onChange={(e) => setContent(e.target.value)}
          placeholder="Enter your content"
        ></textarea>
        <button onClick={handleCreatePost} >Create</button>
      </form>
    </>
  );
};

export default Write;

C. Update Blog

Step 1: Fetch the api endpoint

import axios from "axios"

const API = axios.create({baseURL:"http://localhost:8800/"});

export const fetchBlogByID = (id) => API.get(`/blogs/${id}`);
export const updateBlog = (id,updatedBlog) => API.put(`/blogs/${id}`,updatedBlog)

Step 2: Create actions for this endpoint

import * as api from "../api";
import {
  FETCH_BLOG_ID,
  UPDATE_BLOG,
} from "../constants/actionTypes";

export const getBlogByID = (id) => async (dispatch) => {
  try {
    const { data } = await api.fetchBlogByID(id);
    dispatch({ type: FETCH_BLOG_ID, payload: data });
  } catch (error) {
    console.log(error);
  }
};


export const updateBlog = (id, updatedblog) => async (dispatch) => {
  try {
    const { data } = await api.updateBlog(id, updatedblog);
    dispatch({ type: UPDATE_BLOG, payload: data });
  } catch (error) {
    console.log(error);
  }
};

Step 4: Create reducers for this endpoint

import { FETCH_BLOG_ID, UPDATE_BLOG } from "../constants/actionTypes";

export default (blogs = [], action) => {
  switch (action.type) {
    case FETCH_BLOG_ID:
      return [action.payload];
    case UPDATE_BLOG:
      blogs.map((blog)=> blog._id === action.payload._id ? action.payload : blog );
      break;
      default:
      return blogs;
  }
};

Step 5: Now in the update component

import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from 'react-router-dom'
import { getBlogByID, updateBlog } from "../actions/blogs";

const Update = () => {
  const { id } = useParams();
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [image, setImage] = useState("");
  const navigate = useNavigate();
  const dispatch = useDispatch();

  const blog = useSelector((state) => state?.blogs[0]); 
  useEffect(() => {
    dispatch(getBlogByID(id));
  }, [dispatch, id]);

  useEffect(() => {
    if (blog) {
      setTitle(blog.title || "");
      setImage(blog.image || "");
      setContent(blog.content || "");
    }
  }, [blog]);

  const handleUpdate = async (e) => {
    e.preventDefault();
    const updatedBlog = {
      title,
      content,
      image
    };
    await dispatch(updateBlog(id, updatedBlog));
    navigate("/");
    alert("Updated Post")
  };

  return (
    <>
      <form>
        <input
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="Enter your title"
        />
        <input
          value={image}
          onChange={(e) => setImage(e.target.value)}
          placeholder="Enter your image url"
        />
        <textarea
          value={content}
          onChange={(e) => setContent(e.target.value)}
          placeholder="Enter your content"
        ></textarea>
        <button onClick={handleUpdate}>Update</button>
      </form>
    </>
  );
};

export default Update;

D. Delete Blog

Step 1: Fetch the api endpoint

import axios from "axios"

const API = axios.create({baseURL:"http://localhost:8800/"});

export const deleteBlog = (id) => API.delete(`/blogs/${id}`);

Step 2: Create actions for this endpoint

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

export const deleteBlog = (id) => async (dispatch) => {
  try {
    await api.deleteBlog(id);
    dispatch({ type: DELETE_BLOG, payload: id });
  } catch (error) {
    console.log(error);
  }
};

Step 3: Create reducer from the endpoint

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

export default (blogs = [], action) => {
  switch (action.type) {
    case DELETE_BLOG:
      return blogs.filter((blog) => blog._id !== action.payload._id);
    default:
      return blogs;
  }
};

Step 4: Now in the component which contains delete button

import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getBlogs, deleteBlog } from "../actions/blogs";
import { NavLink } from "react-router-dom";
const Blogs = () => {
  const dispatch = useDispatch();
  const handleDelete = (id) => {
    dispatch(deleteBlog(id));
  };

  return (
    <div>
      {blogs.map((item, index) => {
        return (
          <>
            <div>
              <NavLink to={`/blog/${item._id}`}>
                <h1>{item.title}</h1>
              </NavLink>
              <p>{item.content}</p>
              <img src={item.image} />
                //creating such kind of function is neccessary or it will be 
                //deleting all the blogs even if the button is not pressed
              <button onClick={() => handleDelete(item._id)}>Delete</button>
            </div>
          </>
        );
      })}
    </div>
  );
};

export default Blogs;