Search code examples
javascriptreactjsreduxreact-hooksreact-redux

Why is my `amount` reading as undefined from my `createSlice`?


I have just started learning Redux with React so I apologize if it is something as simple as syntax. This project prompts me to use a variety of react-redux functions.

I have my slice below:

/** @type {{balance: number, history: Transaction[]}} */
const initialState = {balance: 0, history: []};

/* TODO
Add two reducers  to the transactions slice: "deposit" and "transfer".
Both reducers update the balance and then record the transaction.

"deposit" should increase the balance by the amount in the payload,
while "transfer" should decrease the balance by the amount in the payload.

Refer to the "withdrawal" reducer, which is already implemented for you.
*/

const transactionsSlice = createSlice({
  name: "transactions",
  initialState,
  reducers: {
    withdrawal: (state, { payload }) => {
      state.balance -= payload.amount;
      state.history.push({
        type: "withdrawal",
        amount: payload.amount,
        balance: state.balance,
      });
    },
    deposit: (state, { payload }) => {
      state.balance += payload.amount;
      state.history.push({
        type: "deposit",
        amount: payload.amount,
        balance: state.balance,
      });
    },
    transfer: (state, { payload }) => {
      state.balance -= payload.amount;
      state.history.push({
        type: "transfer",
        amount: payload.amount,
        balance: state.balance,
      });
    },
  },
});

export const { deposit, withdrawal, transfer } = transactionsSlice.actions;

export const selectBalance = (state) => state.transactions.balance;
export const selectHistory = (state) => state.transactions.history;

export default transactionsSlice.reducer;

My store:

import { configureStore } from "@reduxjs/toolkit";
import transactionsSlice from "../features/transactions/transactionsSlice";

// TODO: Configure the store to use the reducer from the transactions slice.
export const store = configureStore({
    reducer:{
        transactions:transactionsSlice,
    }
});

And a Transactions.jsx where I am using useDispatch:

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

import "./transactions.scss";

/**
 * Allows users to deposit to, withdraw from, and transfer money from their account.
 */
export default function Transactions() {
  // TODO: Get the balance from the Redux store using the useSelector hook
  const balance = useSelector(state => state.transactions.balance);
  

  const [amountStr, setAmountStr] = useState("0.00");

  const dispatch = useDispatch()

  /** Dispatches a transaction action based on the form submission. */
  const onTransaction = (e) => {
    e.preventDefault();

    // This changes depending on which button the user clicked to submit the form.
    // It will be either "deposit", "withdraw", or "transfer".
    const action = e.nativeEvent.submitter.name;

    const amount = +amountStr;

    
    dispatch({type: `transactions/${action}`, amount: amount});
    // TODO: Dispatch the appropriate transaction action based on `action`
  };

  return (
    <section className="transactions container">
      <h2>Transactions</h2>
      <figure>
        <figcaption>Current Balance &nbsp;</figcaption>
        <strong>${balance.toFixed(2)}</strong>
      </figure>
      <form onSubmit={onTransaction}>
        <div className="form-row">
          <label>
            Amount
            <input
              type="number"
              inputMode="decimal"
              min={0}
              step="0.01"
              value={amountStr}
              onChange={(e) => setAmountStr(e.target.value)}
            />
          </label>
          <div>
            <button default name="deposit">
              Deposit
            </button>
            <button name="withdraw">Withdraw</button>
          </div>
        </div>
        <div className="form-row">
          <label>
            Transfer to
            <input type="text" placeholder="Recipient Name" name="recipient" />
          </label>
          <button name="transfer">Transfer</button>
        </div>
      </form>
    </section>
  );
}

When I run all of this code, I get the error of transactionsSlice.js?t=1715802216497:39 Uncaught TypeError: Cannot read properties of undefined (reading 'amount'). I am not quite sure how I need to initialize the amount that I get from dispatching it.


Solution

  • You need to dispatch

    {type: `transactions/${action}`, payload: { amount: amount }}
    

    but still, please never do that.

    I know you're trying to be clever, but please, use the action creators

    dispatch(deposit({ amount }))
    

    they're there for a reason.

    Especially when you get to the point of using TypeScript, you'll have to refactor everything because the pattern you're introducing here is inherently type-unsafe.