简体   繁体   中英

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: "pineapple@company.com",
    bill_from_address: "86781 547th Ave, Osmond, NE, 68765",
    bill_from_phone: "+(402) 748-3970",
    bill_from_fax: "",
    bill_to_email: "redq@company.com",
    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: "pineapple@company.com",
    bill_from_address: "86781 547th Ave, Osmond, NE, 68765",
    bill_from_phone: "+(402) 748-3970",
    bill_from_fax: "",
    bill_to_email: "redq@company.com",
    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: "pineapple@company.com",
    bill_from_address: "86781 547th Ave, Osmond, NE, 68765",
    bill_from_phone: "+(402) 748-3970",
    bill_from_fax: "",
    bill_to_email: "redq@company.com",
    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!!!

Ps I'm new in stackoveflow, so sorry if something wrong :)

You aren't rerendering the INVOICE_LIST when it changes. You would want to have a useEffect or something similar that will rerender the component when the INVOICE_LIST changes to see any changes on the UI side.

Your problem is this:

{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,
                      }}
                    />
                  ))}

This is rendering static content and will not change even when you make a change to the redux store. You need to change this to state that will rerender when it changes.

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM