Search code examples
javascriptreactjsreduxredux-toolkit

Redux with redux toolkit: UI is not changing


I'm new to redux and redux toolkit in React.js. Trying my best to make my PET project to apply for a future job, but faced a problem. I'll try to describe it.

Firstly, the code. removeInvoice function in invoice-slice.js file:

import { createSlice } from "@reduxjs/toolkit";

import { INVOICES_LIST } from "../Pages/Invoice/InvoicesList";

const invoiceSlice = createSlice({
  name: "invoice",
  initialState: {
    invoices: INVOICES_LIST,
  },
  reducers: {
    addNewInvoice(state, action) {
      const newItem = action.payload;
      state.invoices.push({
        id: newItem.id,
        billFrom: newItem.bill_from,
        billFromAddress: newItem.billFromAddress,
        billTo: newItem.bill_to,
        billToAddress: newItem.bill_to_address,
        invoiceNumber: newItem.invoice_num,
      });
      console.log(newItem);
    },
    removeInvoice(state, action) {
      const id = action.payload;
      state.invoices = state.invoices.filter((item) => item.id !== id);
      console.log(action);
      console.log(state.invoices);
    },
    editInvoice() {},
  },
});

export const invoiceActions = invoiceSlice.actions;

export default invoiceSlice;

INVOICES_LIST looks like this:

export const INVOICES_LIST = [
  {
    id: Math.random().toString(),
    number: Math.random().toFixed(2),
    invoice_num: "#1232",
    bill_from: "Pineapple Inc.",
    bill_to: "REDQ Inc.",
    total_cost: "14630",
    status: "Pending",
    order_date: "February 17th 2018",
    bill_from_email: "[email protected]",
    bill_from_address: "86781 547th Ave, Osmond, NE, 68765",
    bill_from_phone: "+(402) 748-3970",
    bill_from_fax: "",
    bill_to_email: "[email protected]",
    bill_to_address: "405 Mulberry Rd, Mc Grady, NC, 28649",
    bill_to_phone: "+(740) 927-9284",
    bill_to_fax: "+0(863) 228-7064",
    ITEMS: {
      item_name: "A box of happiness",
      unit_costs: "200",
      unit: "14",
      price: "2800",
      sub_total: "133300",
      vat: "13300",
      grand_total: "14630",
    },
  },
  {
    id: Math.random().toString(),
    number: Math.random().toFixed(2),
    invoice_num: "#1232",
    bill_from: "AMD Inc.",
    bill_to: "Intel Inc.",
    total_cost: "14630",
    status: "Delivered",
    order_date: "February 17th 2018",
    bill_from_email: "[email protected]",
    bill_from_address: "86781 547th Ave, Osmond, NE, 68765",
    bill_from_phone: "+(402) 748-3970",
    bill_from_fax: "",
    bill_to_email: "[email protected]",
    bill_to_address: "405 Mulberry Rd, Mc Grady, NC, 28649",
    bill_to_phone: "+(740) 927-9284",
    bill_to_fax: "+0(863) 228-7064",
    ITEMS: {
      item_name: "Unicorn Tears",
      unit_costs: "500",
      unit: "14",
      price: "1700",
      sub_total: "133300",
      vat: "13300",
      grand_total: "14630",
    },
  },
  {
    id: Math.random().toString(),
    number: Math.random().toFixed(2),
    invoice_num: "#1232",
    bill_from: "Apple Inc.",
    bill_to: "Samsung",
    total_cost: "14630",
    status: "Shipped",
    order_date: "February 17th 2018",
    bill_from_email: "[email protected]",
    bill_from_address: "86781 547th Ave, Osmond, NE, 68765",
    bill_from_phone: "+(402) 748-3970",
    bill_from_fax: "",
    bill_to_email: "[email protected]",
    bill_to_address: "405 Mulberry Rd, Mc Grady, NC, 28649",
    bill_to_phone: "+(740) 927-9284",
    bill_to_fax: "+0(863) 228-7064",
    ITEMS: {
      item_name: "Rainbow Machine",
      unit_costs: "700",
      unit: "5",
      price: "3500",
      sub_total: "133300",
      vat: "13300",
      grand_total: "14630",
    },
  },
];

AllInvoices.js file where i map invoices:

import React, { Fragment } from "react";
import { useDispatch, useSelector } from "react-redux";
import { uiActions } from "../../store/ui-slice";
// import { invoiceActions } from "../../store/invoice-slice";
import { INVOICES_LIST } from "../Invoice/InvoicesList";

import Wrapper from "../../UI/Wrapper";
import Card from "../../UI/Card";
import Footer from "../../UI/Footer";
import Button from "../../UI/Button";
// import InvoiceItemDescription from "./InvoiceItemDescription";
// import EditInvoiceItem from "./EditInvoiceItem";

import classes from "./AllInvoices.module.css";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
// import AddInvoiceItem from "./AddInvoiceItem";
import { Link } from "react-router-dom";
import Invoice from "./Invoice";

const AllInvoices = (props) => {
  // const { id } = props;

  const dispatch = useDispatch();

  const toggleSelectOptions = () => {
    dispatch(uiActions.toggleSelectOptions());
  };

  // const removeInvoiceItem = (id) => {
  //   dispatch(invoiceActions.removeInvoice(id));
  // };

  const showMoreOptions = useSelector(
    (state) => state.ui.selectOptionsIsVisible
  );

  // const invoice = useSelector((state) => state.invoices);

  return (
    <Fragment>
      <Wrapper isShrinked={props.isShrinked}>
        <Card>
          <h1 className={classes.header}>Invoice</h1>
          <div className={classes.content}>
            <div className={classes["btn-wrapper"]}>
              <Link to="/invoices/add-invoice">
                <Button>Add Invoice</Button>
              </Link>
            </div>
            <div className={classes.invoices}>
              {showMoreOptions && (
                <ul className={classes.list}>
                  <li>Select all invoices</li>
                  <li>Unselect all</li>
                  <li>Delete selected</li>
                </ul>
              )}
              <table>
                <colgroup>
                  <col className={classes.col1}></col>
                  <col className={classes.col2}></col>
                  <col className={classes.col3}></col>
                  <col className={classes.col4}></col>
                  <col className={classes.col5}></col>
                  <col className={classes.col6}></col>
                  <col className={classes.col7}></col>
                </colgroup>
                <thead className={classes["table-head"]}>
                  <tr>
                    <th className={classes.position}>
                      <span className={classes.checkbox}>
                        <input type="checkbox"></input>
                      </span>
                      <FontAwesomeIcon
                        icon={faChevronDown}
                        className={classes.chevron}
                        onClick={toggleSelectOptions}
                      />
                    </th>
                    <th>
                      <span className={classes["thead-text"]}>Number</span>
                    </th>
                    <th>
                      <span className={classes["thead-text"]}>Bill From</span>
                    </th>
                    <th>
                      <span className={classes["thead-text"]}>Bill To</span>
                    </th>
                    <th>
                      <span className={classes["thead-text"]}>Total Cost</span>
                    </th>
                    <th>
                      <span className={classes["thead-text"]}>Status</span>
                    </th>
                  </tr>
                </thead>
                <tbody>
                  {INVOICES_LIST.map((invoice, index) => (
                    <Invoice
                      key={index}
                      invoiceItem={{
                        id: invoice.id,
                        invoice_num: invoice.invoice_num,
                        bill_from: invoice.bill_from,
                        bill_to: invoice.bill_to,
                        status: invoice.status,
                      }}
                    />
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        </Card>
        <Footer />
      </Wrapper>
    </Fragment>
  );
};

export default AllInvoices;

And Invoice.js file where i should use removeInvoice:

import React from "react";

import classes from "./Invoice.module.css";

import { useDispatch } from "react-redux";
import { Link } from "react-router-dom";

import { invoiceActions } from "../../store/invoice-slice";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

import { faTrash } from "@fortawesome/free-solid-svg-icons";

const Invoice = (props) => {
  const { id, invoice_num, bill_from, bill_to, status } = props.invoiceItem;

  const dispatch = useDispatch();

  const removeInvoiceItem = () => {
    dispatch(invoiceActions.removeInvoice(id));
  };

  return (
    <tr className={classes.height}>
      <td>
        <span className={classes.checkbox}>
          <input type="checkbox"></input>
        </span>
      </td>
      <td>
        <span>{invoice_num}</span>
      </td>
      <td>
        <span>{bill_from}</span>
      </td>
      <td>
        <span>{bill_to}</span>
      </td>
      <td>
        <span>14300</span>
        {/* This should be a dynamic value later */}
      </td>
      <td>
        <span
          className={`${
            status === "Pending" ? classes["status-pending"] : ""
          } ${status === "Delivered" ? classes["status-delivered"] : ""} ${
            status === "Shipped" ? classes["status-shipped"] : ""
          }`}
        >
          {status}
        </span>
      </td>
      <td>
        <div className={classes.buttons}>
          <Link to={`/invoices/invoice-description/${id}`}>
            <button className={classes["view-btn"]}>View</button>
          </Link>
          <button className={classes["delete-btn"]} onClick={removeInvoiceItem}>
            <FontAwesomeIcon icon={faTrash} />
          </button>
        </div>
      </td>
    </tr>
  );
};

export default Invoice;

Now, the issue. It seems to remove the invoice from an array, because array changes from 3 items to 2, and shows in a console payload with appropriate id of item which i wanted to remove by clicking on a button, but UI console.log here doesn't reflect the changes, there is still 3 invoice items instead of 2 or less. Anyone know what can be the problem?

I've already tried alot of variants, like to pass an id to a function like this:

const removeInvoiceItem = (id) => {
dispatch(invoiceActions.removeInvoice(id));

};

Tried to make it with curly braces:

const removeInvoiceItem = (id) => {
dispatch(invoiceActions.removeInvoice({ id }));

};

Also tried anonymous function here:

<button className={classes["delete-btn"]} onClick={() => removeInvoiceItem(id)}>
        <FontAwesomeIcon icon={faTrash} />
      </button>

And so on. I know that if UI doesn't change, then state is not changing, but, in my case, i overwrite the state like this:

state.invoices = state.invoices.filter((item) => item.id !== id);

So, i don't know what else to do. Thought of useSelector and tried it like this:

const invoice = useSelector((state) => state.invoices);

And in map function:

{invoice.map((invoice, index) => (
                <Invoice
                  key={index}
                  invoiceItem={{
                    id: invoice.id,
                    invoice_num: invoice.invoice_num,
                    bill_from: invoice.bill_from,
                    bill_to: invoice.bill_to,
                    status: invoice.status,
                  }}
                />
              ))}

But it was crashing the page and telling me this error: Uncaught TypeError: invoice.map is not a function.

So, i don't know what to do else. Please, help me!!!

P.s. I'm new in stackoveflow, so sorry if something wrong :)


Solution

  • The problem is that you're using the constant INVOICE_LIST to map your elements instead of the current state of the store.

    You used INVOICE_LIST to initialize your slice, that's good. But then you did not use what you initialized, you simply used the constant, and that's why the UI remained constant.

    You should use useSelector to access that state like so:

    const invoiceList = useSelector(state => state.invoice.invoices)
    

    This should be the correct syntaxe in your case:

    state.sliceName.wantedProperty
    

    Now when you map on "invoiceList" instead of "INVOICE_LIST", this should do the trick!