1

I'm doing my PET project with Redux toolkit, but have some troubles with inputs. When i add an input on click it adds correctly, but i'm not sure it adds in right place(it's should be added in ITEMS array). Other issue is with it's data. If i add 2 inputs: 1) Item Name: 'Iphone 13', Unit Costs: '1200', unit: '2'; 2) Item Name: 'Iphone 12', Unit Costs: '1100', unit: '1'. It gonna add only last one and array length would be only 1.

Why is it hapenning? What i did wrong?

I've already tried using spread operator, but it shows error when i click on Add Item button.

Now the code.

InvoicesList.js file with INVOICES_LIST array which includes ITEMS where should be all items from inputs.

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",
      },
    ],
  },
];

AddInvoiceItem.js file where you can find inputs and adding logic.

import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";

import { uiActions } from "../../store/ui-slice";

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

import { useFormik } from "formik";

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

import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCalendar } from "@fortawesome/free-solid-svg-icons";

import { faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { invoiceActions } from "../../store/invoice-slice";
import { Link } from "react-router-dom";

const AddInvoiceItem = (props) => {
  const date = new Date();

  const options = ["Pending", "Shipped", "Delivered"];

  const inputs = [{ itemName: "", unitCosts: "", unit: "" }];

  const [startDate, setStartDate] = useState(date);
  const [selectedOption, setSelectedOption] = useState(options[0]);
  const [listItems, setListItems] = useState(inputs);

  const optionClickHandler = (value) => () => {
    setSelectedOption(value);
    dispatch(uiActions.toggleMoreOptions());
  };

  const addInvoiceHandler = (invoice) => {
    console.log(invoice);
    console.log(selectedOption);
    dispatch(
      invoiceActions.addNewInvoice({
        id: Math.random(),
        invoiceNumber: invoice.invoiceNumber,
        billFrom: invoice.billFrom,
        billFromAddress: invoice.billFromAddress,
        billTo: invoice.billTo,
        billToAddress: invoice.billToAddress,
        status: selectedOption,
        order_date: startDate.toJSON(),
        ITEMS: [
          {
            id: Math.random(),
            item_name: invoice.itemName,
            unit_costs: invoice.unitCosts,
            units: invoice.unit,
          },
        ],
      })
    );
  };

  const formikInvoice = useFormik({
    initialValues: {
      invoiceNumber: "",
      billFrom: "",
      billFromAddress: "",
      billTo: "",
      billToAddress: "",
      status: selectedOption,
      date: startDate.toJSON(),
      ITEMS: [
        {
          id: Math.random(),
          itemName: "",
          unitCosts: "",
          unit: "",
        },
      ],
    },
    onSubmit: (val) => {
      addInvoiceHandler(val);
    },
  });

  const dispatch = useDispatch();

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

  const showOtherOptions = useSelector(
    (state) => state.ui.selectMoreOptionsIsVisible
  );

  let counter = 1;

  const addItemHandler = () => {
    setListItems(listItems.concat([{ itemName: "", unitCosts: "", unit: "" }]));
  };

  return (
    <form onSubmit={formikInvoice.handleSubmit}>
      <Wrapper isShrinked={props.isShrinked}>
        <Card>
          <div className={classes.content}>
            <div className={classes["buttons-wrapper"]}>
              <Link to="/invoices">
                <button type="button" className={classes["cancel-btn"]}>
                  Cancel
                </button>
              </Link>
              {/* <Link to="/invoices"> */}
              <Button className={classes["save-btn"]}>Save</Button>
              {/* </Link> */}
            </div>
            <div className={classes["invoice-info-wrapper"]}>
              <div className={classes["invoice-info"]}>
                <h3>Invoice Info</h3>
                <input
                  placeholder="Number"
                  type="text"
                  name="invoiceNumber"
                  id="invoiceNumber"
                  onChange={formikInvoice.handleChange}
                  value={formikInvoice.values.invoiceNumber}
                  onBlur={formikInvoice.handleBlur}
                ></input>
              </div>
              <div className={classes["right-side-column"]}>
                <div className={classes["order-status"]}>
                  <span>Order Status: </span>
                  <div className={classes.buttons}>
                    {showOtherOptions && (
                      <ul className={classes.options}>
                        {options.map((option) => (
                          <li
                            onClick={optionClickHandler(option)}
                            key={Math.random()}
                          >
                            {option}
                          </li>
                        ))}
                      </ul>
                    )}
                    <button type="button" className={classes.status}>
                      {selectedOption}
                    </button>
                    <button
                      type="button"
                      className={classes.dots}
                      onClick={toggleMoreOptions}
                    >
                      <FontAwesomeIcon icon={faEllipsis} />
                    </button>
                  </div>
                </div>
                <div className={classes["order-date"]}>
                  <span>Order Date:</span>
                  <DatePicker
                    className={classes["order-date-input"]}
                    selected={startDate}
                    onChange={(val) => setStartDate(val)}
                  />
                  <FontAwesomeIcon
                    icon={faCalendar}
                    className={classes.calendar}
                  ></FontAwesomeIcon>
                </div>
              </div>
            </div>
            <div className={classes["order-bills"]}>
              <div className={classes["bill-from"]}>
                <input
                  placeholder="Bill From"
                  type="text"
                  name="billFrom"
                  id="billFrom"
                  onChange={formikInvoice.handleChange}
                  value={formikInvoice.values.billFrom}
                  onBlur={formikInvoice.handleBlur}
                ></input>
                <textarea
                  placeholder="Bill From Address"
                  name="billFromAddress"
                  id="billFromAddress"
                  onChange={formikInvoice.handleChange}
                  value={formikInvoice.values.billFromAddress}
                  onBlur={formikInvoice.handleBlur}
                ></textarea>
              </div>
              <div className={classes["bill-to"]}>
                <input
                  placeholder="Bill To"
                  type="text"
                  name="billTo"
                  id="billTo"
                  onChange={formikInvoice.handleChange}
                  value={formikInvoice.values.billTo}
                  onBlur={formikInvoice.handleBlur}
                ></input>
                <textarea
                  placeholder="Bill To Address"
                  name="billToAddress"
                  id="billToAddress"
                  onChange={formikInvoice.handleChange}
                  value={formikInvoice.values.billToAddress}
                  onBlur={formikInvoice.handleBlur}
                ></textarea>
              </div>
            </div>
            <div className={classes["table-wrapper"]}>
              <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>
                </colgroup>
                <thead>
                  <tr>
                    <td className={classes["more-padding"]}>#</td>
                    <td>Item Name</td>
                    <td>Unit Costs</td>
                    <td>Unit</td>
                    <td>Price</td>
                    <td></td>
                  </tr>
                </thead>
                <tbody>
                  {listItems.map((item, index) => (
                    <tr key={index}>
                      <td className={classes["more-padding"]}>{counter++}</td>
                      <td>
                        <input
                          placeholder="Item Name"
                          className={classes.inputs}
                          name="itemName"
                          id="itemName"
                          onChange={formikInvoice.handleChange}
                          value={formikInvoice.values.item_name}
                          onBlur={formikInvoice.handleBlur}
                        ></input>
                      </td>
                      <td>
                        <input
                          placeholder="Unit Costs"
                          className={classes.inputs}
                          name="unitCosts"
                          id="unitCosts"
                          onChange={formikInvoice.handleChange}
                          value={formikInvoice.values.unit_costs}
                          onBlur={formikInvoice.handleBlur}
                        ></input>
                      </td>
                      <td>
                        <input
                          placeholder="Unit"
                          className={classes.inputs}
                          name="unit"
                          id="unit"
                          onChange={formikInvoice.handleChange}
                          value={formikInvoice.values.units}
                          onBlur={formikInvoice.handleBlur}
                        ></input>
                      </td>
                      <td>0</td>
                      <td></td>
                      {/* There should be dynamic values later */}
                    </tr>
                  ))}
                </tbody>
              </table>
              <div className={classes["add-item-btn"]}>
                <button
                  onClick={addItemHandler}
                  type="button"
                  className={classes["add-item-btn"]}
                >
                  Add Item
                </button>
              </div>
              <div className={classes.total}>
                <p className={classes["sub-total"]}>
                  <span>Sub Total: </span>
                  <span>$0</span>
                  {/* Dynamic value later here */}
                </p>
                <div className={classes["total-vat"]}>
                  <span>Total Vat:</span>
                  <div className={classes["total-sum"]}>
                    <span className={classes["input-wrapper"]}>
                      <input type="text" defaultValue="10"></input>
                      <span>%</span>
                    </span>
                    <span className={classes.sum}>$0</span>
                    {/* Dynamic value later here */}
                  </div>
                </div>
                <div className={classes["grand-total"]}>
                  <h3>Grand Total</h3>
                  <div className={classes.input}>
                    <input type="text" defaultValue="$"></input>
                    <span>0</span>
                    {/* Dynamic value later here */}
                  </div>
                </div>
              </div>
            </div>
            <div className={classes.dummy}></div>
          </div>
        </Card>
        <Footer />
      </Wrapper>
    </form>
  );
};

export default AddInvoiceItem;

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,
        status: newItem.status,
        order_date: newItem.order_date,
        ITEMS: [
          {
            id: Math.random(),
            item_name: newItem.item_name,
            unit_costs: newItem.unit_costs,
            units: newItem.units,
          },
        ],
      });
      console.log(newItem);
    },
    removeInvoice(state, action) {
      const id = action.payload;
      state.invoices = state.invoices.filter((item) => item.id !== id);
    },
    editInvoice() {},
  },
});

export const invoiceActions = invoiceSlice.actions;

export default invoiceSlice;

Also: should i give every item unique id? Because, i'm not sure if i need that.

P.S. here is github repo - https://github.com/stepan-slyvka/test-project

P.P.S. this is my PET project, so don't wonder why I'm using Context in one page and another Redux. It's only for learning purpose.

1 Answer 1

1

I've looked at your code now. This is one way to get it all to work, but, in general you should probably make a decision whether to use formik's state as the main state for listItems and refactor your code into that direction or do something else.

However, here is one ROUGH way to solve your problem, use it as a reference in making decisions on how to manage it.

Add the following to your AddinvoiceItem.js:

  const updateItemHandler = (index, inputName, value) => {
    listItems[index] = {...listItems[index], [inputName]: value};
  }

  const updateValuesOnSubmit = () => {
    return listItems;
  }

and change every input (itemName, unitCosts, unit) to use the new function in its onChange parameter:

 onChange={(e) => updateItemHandler(index, 'THIS has to be the name of the input, for example itemName', e.currentTarget.value)}

Change addInvoiceHandler to use new function in its dispatch:

dispatch(
      invoiceActions.addNewInvoice({
        id: Math.random(),
        invoiceNumber: invoice.invoiceNumber,
        billFrom: invoice.billFrom,
        billFromAddress: invoice.billFromAddress,
        billTo: invoice.billTo,
        billToAddress: invoice.billToAddress,
        status: selectedOption,
        order_date: startDate.toJSON(),
        ITEMS: [
      ...updateValuesOnSubmit()
        ],
      })
    );
  };

and finally, go to invoice-slice.js and do this:

 ITEMS: [
          ...newItem.ITEMS
        ],

This is how you should be able to get multiple items within an invoice to your redux state. Let me emphasize once more that you should still consider using Formik's state and its management, handleChange features etc. as you've already chosen it for this task.

But this is still one way to do what you wanted to do. Hope it helps!

Sign up to request clarification or add additional context in comments.

3 Comments

Okay, i'll try this and answer u if i succeed.
Thank you! This helped, but you're right i should consider about working only with Formik and only without formik, like u did it.
No problem, glad that it helped! Exactly, to choose one way or the other and stick to it in your logic. Formik isn't that familiar to me but it seems that it should work nicely once you use it as the center piece of the logic, but I'll leave that to you to implement:) Cheers!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.