Search code examples
javascriptnode.jsreactjsreduxredux-toolkit

TypeError: Cannot read properties of undefined (reading 'rejectWithValue') - Redux Toolkit


I am building a full stack mern application with basic crud operations. So far, i have completed the backend rest api and have tested in postman, all endpoints work fine here. ON the frontend, I can create a lead, delete a lead and view the lead fine however, when trying to update the lead, I get a typerror saying 'Cannot read properties of undefined (reading 'rejectWithValue')'. I can seem to understand the issue at hand.

leadService.js

import axios from "axios";

const API_URL = "/api/leads";

// Create new lead
const createLead = async (leadData, token) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  };

  const response = await axios.post(API_URL, leadData, config);

  return response.data;
};

// Get user leads
const getLeads = async (token) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  };

  const response = await axios.get(API_URL, config);

  return response.data;
};

// Get single lead
const getSingleLead = async (id, token) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  };

  const response = await axios.get(API_URL + `/${id}`, config);

  return response.data;
};

// Delete user lead
const deleteLead = async (leadId, token) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  };

  const response = await axios.delete(API_URL + "/" + leadId, config);

  return response.data;
};

// Update lead
const updateLead = async (leadId, leadData, token) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  };

  const response = await axios.put(API_URL + "/" + leadId, leadData, config);

  return response.data;
};

const leadService = {
  createLead,
  getLeads,
  deleteLead,
  getSingleLead,
  updateLead,
};

export default leadService;

leadSlice.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import leadService from "./leadService";

const initialState = {
  leads: [],
  lead: [],
  isError: false,
  isSuccess: false,
  isLoading: false,
  message: "",
};

// Create new lead
export const createLead = createAsyncThunk(
  "leads/create",
  async (leadData, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      return await leadService.createLead(leadData, token);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// Get user lead
export const getLeads = createAsyncThunk(
  "leads/getAll",
  async (_, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      return await leadService.getLeads(token);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// get single user lead
export const getSingleLead = createAsyncThunk(
  "leads/getSingle",
  async (id, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      return await leadService.getSingleLead(id, token);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// Delete user lead
export const deleteLead = createAsyncThunk(
  "leads/delete",
  async (id, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      return await leadService.deleteLead(id, token);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// Update lead
export const updateLead = createAsyncThunk(
  "leads/update",
  async (leadId, leadData, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      return await leadService.updateLead(leadId, leadData, token);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

export const leadSlice = createSlice({
  name: "lead",
  initialState,
  reducers: {
    reset: (state) => initialState,
    clear: (state) => {
      state.isSuccess = false;
      state.isError = false;
    },
    filterSearchResults: (state, action) => {
      state.leads = state.leads.filter(
        (lead) =>
          lead.company_name
            .toLowerCase()
            .includes(action.payload.toLowerCase()) && lead
      );
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(createLead.pending, (state) => {
        state.isLoading = true;
        state.message = "";
        state.isError = false;
        state.isSuccess = false;
      })
      .addCase(createLead.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.leads.unshift(action.payload);
      })
      .addCase(createLead.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(getLeads.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(getLeads.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.leads = action.payload;
      })
      .addCase(getLeads.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(getSingleLead.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(getSingleLead.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.lead = action.payload;
      })
      .addCase(getSingleLead.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(deleteLead.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(deleteLead.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.leads = state.leads.filter(
          (lead) => lead._id !== action.payload.id
        );
      })
      .addCase(deleteLead.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(updateLead.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(updateLead.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.leads = state.leads.map((lead) =>
          lead._id !== action.payload.id
            ? {
                ...lead,
                company_name: action.payload.company_name,
                first_name: action.payload.first_name,
                last_name: action.payload.last_name,
                email: action.payload.email,
                position: action.payload.position,
                website: action.payload.website,
                notes: action.payload.notes,
                status: action.payload.status,
              }
            : lead
        );
      })
      .addCase(updateLead.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      });
  },
});

export const { reset, clear, filterSearchResults } = leadSlice.actions;
export default leadSlice.reducer;

EditForm.js

import React, { useEffect, useState } from "react";
import Button from "@mui/material/Button";
import ClearIcon from "@mui/icons-material/Clear";
import { useDispatch } from "react-redux";
import { resetModal } from "../../features/modal/modalSlice";
import { updateLead } from "../../features/leads/leadSlice";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import { useSelector } from "react-redux";
function EditForm() {
  const { isError, isSuccess, lead, message } = useSelector(
    (state) => state.lead
  );

  const [leadData, setLeadData] = useState({
    company_name: lead.company_name,
    first_name: lead.first_name,
    last_name: lead.last_name,
    email: lead.email,
    position: lead.position,
    website: lead.website,
    notes: lead.notes,
    status: lead.status,
  });

  const {
    company_name,
    first_name,
    last_name,
    email,
    position,
    website,
    notes,
    status,
  } = leadData;

  const dispatch = useDispatch();
  const navigate = useNavigate();

  useEffect(() => {
    if (isError) {
      toast.error("Lead not updated");
      dispatch(resetModal());
    }

    if (isSuccess) {
      toast.success("Lead updated");
      dispatch(resetModal());
      navigate();
    }
  });

  const handleClick = () => {
    dispatch(resetModal());
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    dispatch(updateLead(lead._id, leadData));
  };

  const onChange = (e) => {
    setLeadData((prevState) => ({
      ...prevState,
      [e.target.name]: e.target.value,
    }));
  };
  return (
    <div className="form">
      <ClearIcon
        sx={{ float: "right", cursor: "pointer" }}
        onClick={handleClick}
      />
      <h1 className="form__header">Edit Lead</h1>
      <form className="form__container" onSubmit={handleSubmit}>
        <label>Company Name</label>
        <input
          type="text"
          name="company_name"
          onChange={onChange}
          className="form__input"
          value={company_name}
        />
        <label>First Name</label>
        <input
          type="text"
          name="first_name"
          onChange={onChange}
          className="form__input"
          value={first_name}
        />
        <label>Last Name</label>
        <input
          type="text"
          name="last_name"
          onChange={onChange}
          className="form__input"
          value={last_name}
        />
        <label>Email</label>
        <input
          type="email"
          name="email"
          onChange={onChange}
          className="form__input"
          value={email}
        />
        <label>Position</label>
        <input
          type="text"
          name="position"
          onChange={onChange}
          className="form__input"
          value={position}
        />
        <label>Website</label>
        <input
          type="text"
          name="website"
          onChange={onChange}
          className="form__input"
          value={website}
        />
        <label>Notes</label>
        <textarea
          name="notes"
          onChange={onChange}
          className="form__input form__textarea"
          value={notes}
        />
        <label>Status</label>
        <input
          type="text"
          name="status"
          onChange={onChange}
          className="form__input"
          value={status}
        />
        <Button
          size="large"
          type="submit"
          id={lead._id}
          sx={{
            color: "#fff",
            background: "var(--primary)",
            textTransform: "initial",
            fontWeight: "initial",
            border: "none",
            "&:hover": {
              backgroundColor: "var(--primary)",
              border: "none",
            },
          }}
          variant="outlined"
        >
          Submit
        </Button>
      </form>
    </div>
  );
}

export default EditForm;

Solution

  • You can use an object as the parameter and deconstructor it.

    export const updateLead = createAsyncThunk(
      "leads/update",
      async ({leadId, leadData}, thunkAPI) => {
        try {
          const token = thunkAPI.getState().auth.user.token;
          return await leadService.updateLead(leadId, leadData, token);
        } catch (error) {
          const message =
            (error.response &&
              error.response.data &&
              error.response.data.message) ||
            error.message ||
            error.toString();
          return thunkAPI.rejectWithValue(message);
        }
      }
    );
    
      const handleSubmit = (e) => {
        e.preventDefault();
        dispatch(updateLead({leadId: lead._id, leadData}));
      };