Page crashes(reading properties of undefined) in React.js

Viewed 82

In my project I'm using redux toolkit and react router v6. I have invoices list with 'View' button and when it's clicked it should open page with description about invoice. Also i have add invoice feature. When invoice added and I click on 'View' button page crashes and the error in console say:

Uncaught TypeError: Cannot read properties of undefined (reading 'invoice_num')

Error here

And the same happens if i click on existing item and reload the page. It says the error occured in InvoiceItem.js page.

Now the code. InvoiceItem.js

import React from "react";

import { useParams } from "react-router-dom";

import InvoiceItemDescription from "../Invoice/InvoiceItemDescription";

import { INVOICES_LIST } from "./InvoicesList";

const InvoiceItem = () => {
  const params = useParams();

  const invoice = INVOICES_LIST.find(
    (invoice) => invoice.id === params.invoiceId
  );

  return (
    <InvoiceItemDescription
      invoiceNumber={invoice.invoice_num}
      status={invoice.status}
      order_date={invoice.order_date}
      bill_from={invoice.bill_from}
      bill_from_address={invoice.bill_from_address}
      bill_from_email={invoice.bill_from_email}
      bill_from_fax={invoice.bill_from_fax}
      bill_from_phone={invoice.bill_from_phone}
      bill_to={invoice.bill_to}
      bill_to_address={invoice.bill_to_address}
      bill_to_email={invoice.bill_to_email}
      bill_to_fax={invoice.bill_to_fax}
      bill_to_phone={invoice.bill_to_phone}
      item_name={invoice.ITEMS.item_name}
      unit_costs={invoice.ITEMS.unit_costs}
      unit={invoice.ITEMS.unit}
      price={invoice.ITEMS.price}
    />
  );
};

export default InvoiceItem;

InvoiceItemDescription.js file

import React from "react";

import Wrapper from "../../UI/Wrapper";
import Footer from "../../UI/Footer";

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

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

const InvoiceItemDescription = (props) => {
  let counter = 1;

  return (
    <Wrapper isShrinked={props.isShrinked}>
      <div className={classes.wrapper}>
        <div className={classes["content-wrapper"]}>
          <div className={classes["main-wrapper"]}>
            <div className={classes["upper-buttons"]}>
              <div className={classes["upper-buttons-wrapper"]}>
                <Link to="/invoices">
                  <button type="button" className={classes["go-to-invoices"]}>
                    Go To Invoices
                  </button>
                </Link>
                <Link to="/invoices/edit-invoice">
                  <button type="button" className={classes["edit-invoice"]}>
                    Edit Invoice
                  </button>
                </Link>
              </div>
            </div>
            <div className={classes.content}>
              <div className={classes["invoice-info"]}>
                <div className={classes.info}>
                  <h3>Invoice Info</h3>
                  <span>{props.invoiceNumber}</span>
                </div>
                <div className={classes.order}>
                  <p>
                    <span className={classes["order-status"]}>
                      Order Status:
                    </span>
                    <span className={classes.status}>{props.status}</span>
                  </p>
                  <p>
                    <span className={classes["order-date"]}>Order Date:</span>
                    <span className={classes.date}>{props.order_date}</span>
                  </p>
                </div>
              </div>
              <div className={classes.bills}>
                <div className={classes["bill-from"]}>
                  <h3>Bill From</h3>
                  <div>
                    <p className={classes["bill-from-info"]}>
                      <span className={classes.name}>{props.bill_from}</span>
                      <span className={classes.email}>
                        {props.bill_from_email}
                        <br></br>
                        <br></br> {props.bill_from_address}
                        <br></br>
                        <br></br>
                        <br></br> {props.bill_from_phone}
                      </span>
                    </p>
                  </div>
                </div>
                <div className={classes["bill-to"]}>
                  <h3>Bill To</h3>
                  <p className={classes["bill-to-info"]}>
                    <span className={classes.name}>{props.bill_to}</span>
                    <span className={classes.email}>
                      {props.bill_to_email} <br></br>
                      <br></br> {props.bill_to_address} <br></br>
                      <br></br>
                      <br></br>
                      {props.bill_to_fax} <br></br> {props.bill_to_phone}
                    </span>
                  </p>
                </div>
              </div>
              <div className={classes.table}>
                <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>
                  </colgroup>
                  <thead>
                    <tr>
                      <td>#</td>
                      <td>Item Name</td>
                      <td>Unit Costs</td>
                      <td>Unit</td>
                      <td>Price</td>
                    </tr>
                  </thead>
                  <tbody>
                    <tr>
                      <td>{counter++}</td>
                      <td>{props.item_name}</td>
                      <td>{props.unit_costs}</td>
                      <td>{props.unit}</td>
                      <td>{props.price}</td>
                    </tr>
                  </tbody>
                </table>
              </div>
              <div className={classes.total}>
                <p>
                  Sub-total:
                  <span>$13300</span>
                </p>
                <p>
                  Vat:
                  <span>$13300</span>
                </p>
                <h3>
                  Grand Total:
                  <span>$14630</span>
                </h3>
              </div>
            </div>
            <div className={classes["lower-btn"]}>
              <button type="button">Send Invoice</button>
            </div>
          </div>
        </div>
      </div>
      <Footer />
    </Wrapper>
  );
};

export default InvoiceItemDescription;

And Invoice.js file

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;

I have no idea what can cause the crash of the page. Can someone help me with this, please?

P.S. here is my github repo(it's my PET project) - https://github.com/stepan-slyvka/test-project

2 Answers

The issue is that in the INVOICES_LIST test invoice data exported from src/components/Pages/Invoice/InvoicesList.js you are creating randomly generated id properties. When the page reloads, the entire app reloads, INVOICES_LIST is exported with all new id properties. The id that is read from the URL path is no longer valid and InvoiceItem can't render an invoice object that is undefined.

export const INVOICES_LIST = [
  {
    id: Math.random().toString(), // <-- random each time app loads
    ...
  },
  {
    id: Math.random().toString(), // <-- random each time app loads
    ...
  },
  {
    id: Math.random().toString(), // <-- random each time app loads
    ...
  },
];

You really want GUIDs to be stable, and more determinant and guaranteed for uniqueness, so don't use Math.random to create them, use something more like uuid if you need to generate unique ids.

To resolve your specific issue the fix is to just hardcode a unique id value. Even just complete gibberish, so long as it uniquely identifies an object, is sufficient (for testing).

Example:

export const INVOICES_LIST = [
  {
    id: '09u34otiuhnrfgp9ioj45',
    ...
  },
  {
    id: '234098ujh43gikoljaerpgiojaerg',
    ...
  },
  {
    id: '0934tpinr-9ujw3ensdsf',
    ...
  },
];

In the InvoiceItem component that is searching the INVOICES_LIST array keep in mind that Array.prototype.find potentially returns undefined when no match is found. The UI should handle this. Conditionally render the InvoiceItemDescription only if there is a found invoice.

Example:

const InvoiceItem = () => {
  const { invoiceId } = useParams();

  const invoice = INVOICES_LIST.find((invoice) => invoice.id === invoiceId);

  return invoice ? (
    <InvoiceItemDescription
      invoice_num={invoice.invoice_num}
      status={invoice.status}
      order_date={invoice.order_date}
      bill_from={invoice.bill_from}
      bill_from_address={invoice.bill_from_address}
      bill_from_email={invoice.bill_from_email}
      bill_from_fax={invoice.bill_from_fax}
      bill_from_phone={invoice.bill_from_phone}
      bill_to={invoice.bill_to}
      bill_to_address={invoice.bill_to_address}
      bill_to_email={invoice.bill_to_email}
      bill_to_fax={invoice.bill_to_fax}
      bill_to_phone={invoice.bill_to_phone}
      item_name={invoice.ITEMS.item_name}
      unit_costs={invoice.ITEMS.unit_costs}
      unit={invoice.ITEMS.unit}
      price={invoice.ITEMS.price}
    />
  ) : (
    <div>No Invoices Found.</div>
  );
};

Edit page-crashesreading-properties-of-undefined-in-react-js

So, issue is fixed and to conclude, maybe someone is facing the same problem, I'll write a few words.

Firstly, when you have static data like I had(3 invoices which renders each time page loads) - hardcode your id's.

Secondly, for newly created items use stable id's(example - uuid) and store it somewhere(for example in local storage).

Thirdly, if page crashes or something renders wrong - check your state! In my case issue was in InvoiceItem.js file where i was trying to render INVOICES_LIST instead of selecting my state with const invoices = useSelector((state) => state.invoice.invoices)

Hope, this helps someone like me - novice in redux :)

P.S. final code is here

Related