Help with Expense Tracker (step 3 and 7)

Hi, I’m trying to complete the Expense Tracker project, but I ran into a problem. Could someone help me why my editBudget case reducer doesn’t update the state with the new amount from action.payload , and the addTransaction / deleteTransaction case reducer don’t work either.

const budgetsSlice = createSlice({
  name: 'budgets',
  initialState: initialState,
  reducers: {
    editBudget: (state, action) => {
      const index = state.budgets.findIndex((budgetObject) => budgetObject.category === action.payload.category);
      state.budgets[index] = payload;
      return state;
    }
  }
});
const transactionsSlice = createSlice({
  name: 'transactions',
  initialState: initialState,
  reducers: {
    addTransaction: (state, action) => {
      return state.transactions[action.payload.category].push(action.payload);
    },

    deleteTransaction: (state, action) => {
      return state.transactions[action.payload.category].filter(transactionCategory => transactionCategory !== action.payload.category);
    }
  }
});

https://www.codecademy.com/paths/full-stack-engineer-career-path/tracks/fscp-redux/modules/refactoring-with-redux-toolkit/projects/redux-expense-tracker

1 Like

okay so I figured out where my logic went wrong. I mistakenly though that the state object includes everything, like budgets and transactions properties. I then realized because of the selector function defined at the end of the file, the state keyword only provide access to the budget property. In other words, state gives me an array like this:

[ 
    { category: 'housing', amount: 400 },
    { category: 'food', amount: 100 },
    ...
  ]

so my editBudget case reducer can be written out like so:

editBudget: (state, action) => {
      const index = state.findIndex(obj => obj.category === action.payload.category);
      return state[index] = action.payload;
      }

the transactionsSlice

const transactionsSlice = createSlice({
  name: 'transactions',
  initialState: initialState,
  reducers: {
    addTransaction: (state, action) => {
      state[action.payload.category].push(action.payload);
      return state;
    },

    deleteTransaction: (state, action) => {
      state[action.payload.category].filter(transaction => transaction.id !== action.payload.id);
      return state;
    }
  }
});

of course it can be refactor even more if you’re so inclined :slight_smile:

5 Likes

Hey edpho,

So I was stuck on the same issues but after some tweaking, I realized my issue was that I was “returning” something from these reducers. I tried your solutions and the numbers wouldn’t update. however, when I remove the “return state;” from the reducers it worked. I’m still trying to wrap my head around why a return of some sort isn’t necessary (im assuming it has to do with Immer altering state for us) but just wanted to point it out for anyone else trying this out.

my solution for step 3 looks like this:

editBudget: (state, action) => {
      state.map(budget => {
        if (budget.category === action.payload.category) {
          budget.amount = action.payload.amount
        }
        return budget;
      });
    }

and my solution for step 7 looks like this:

const transactionsSlice = createSlice({
  name: 'transactions',
  initialState: initialState,
  reducers: {
    addTransaction: (state, action) => {
      state[action.payload.category].push(action.payload)
    },
    deleteTransaction: (state, action) => {
      const index = state[action.payload.category].findIndex(tx => tx.id === action.payload.id);
      state[action.payload.category].splice(index, 1);
    }
  }
})
2 Likes

My GIthub link for the Expense Tracker project

Expense Tracker

Before we get started, let’s spend some time using the app in its current implementation to ensure we understand how it’s supposed to work.
Note: I suggest to rebuild our App’s file structure for a more comprehensive understanding (as shown in the first commit).

Create a Budgets Slice

At the top of budgetsSlice.js:

1a. Import createSlice from @reduxjs/toolkit.
Redux Toolkit Documentation

import createSlice from '@reduxjs/toolkit';

Define a slice by calling createSlice() with a configuration object containing the required name, initialState, and reducers properties. Redux Toolkit Documentation

2a. Define a variable, budgetsSlice, and initialize it with a call to createSlice(), passing in an empty configuration object. Do this right after the line defining initialState.

const budgetsSlice = createSlice({

});

2b. Slices are conventionally named for the resource whose state they manage. This slice manages budgets and should be named accordingly. To give the slice a name, add a name property to the configuration object and set it equal to ‘budgets’.

const budgetsSlice = createSlice({
  name: 'budgets',
});

2c. Add an initialState property to the configuration object, and set it equal to the variable initialState that we’ve defined for you.

const budgetsSlice = createSlice({
  name: 'budgets',
  initialState: initialState,
});

2d. Lastly, you’ll need to include a reducers property in the configurations object. For now, set it equal to an empty object.

const budgetsSlice = createSlice({
  name: 'budgets',
  initialState: initialState,
  reducers: {

  },
});

3a. Add an editBudget property to the reducers object passed to createSlice().

const budgetsSlice = createSlice({
  name: 'budgets',
  initialState: initialState,
  reducers: {

  },
});

3b. Set editBudget equal to a case reducer that receives two arguments—state and action . action.payload will have a category and amount property.

const budgetsSlice = createSlice({
  name: 'budgets',
  initialState: initialState,
  reducers: {
    // Set editBudget equal to a case reducer that receives two arguments—state and action
    editBudget: (state, action) => {
      // action.payload will have a category and amount property.
      const {category, amount} = action.payload;
    }
  },
});

3c. editBudget should update the state by finding the budget object whose .category value matches action.payload.category and changing with the .amount value to action.payload.amount.

const budgetsSlice = createSlice({
  name: 'budgets',
  initialState: initialState,
  reducers: {
    // Set editBudget equal to a case reducer that receives two arguments—state and action
    editBudget: (state, action) => {
      // action.payload will have a category and amount property.
      // const {category, amount} = action.payload;
      const category = action.payload.category;
      const amount = action.payload.amount;
      // Update the state by finding the budget object
      // Note: the variables category and action, implemented below, are each assigned action.payload (referenced in the above const). 
      // Ex. category = action.payload.category ;
      // Ex. amount = action.payload.category;
      // The budget object whose .category value matches action.payload.category and changing with the .amount value to action.payload.amount.
      state.find(budget => budget.category === category).amount = amount 
    }
  },
});

Delete your old code and clean up your exports.

4a. Delete the stand-alone editBudget. At the bottom of the file budgetsSlice.js, export the editBudget action creator generated by createSlice() and stored in budgetsSlice.

// export const { myActionCreator } = mySlice.actions;
export const { editBudget } = budgetsSlice.actions;

4b. Delete the stand-alone budgetsReducer, and update the export default statement to export the reducer generated by createSlice() and stored in budgetsSlice.

// export default mySlice.reducer;
export default budgetsSlice.reducer;
Checkpoint 1: We are now able to edit budgets and see out changes reflected in the app.

In transactionsSlice.js:

5a. Import createSlice from @reduxjs/toolkit.

import createSlice from '@reduxjs/toolkit';

Define a slice by calling createSlice() with a configuration object containing the required name, initialState, and reducers properties.

6a. Define a variable, transactionsSlice, and initialize it with a call to createSlice(), passing in an empty configuration object.

const transactionsSlice = createSlice({});

6b. Add a name property to the configuration object and set it equal to ‘transactions’.

const transactionsSlice = createSlice({
  name: 'transactions',
});

6c. Add an initialState property to the configuration object, and set it equal to the variable initialState that we’ve defined for you.

const transactionsSlice = createSlice({
  name: 'transactions',
  initialState: initialState,
});

6d. Lastly, you’ll need to include a reducers property in the configurations object. For now, set it equal to an empty object.

const transactionsSlice = createSlice({
  name: 'transactions',
  initialState: initialState,
  reducers: {},
});

Replace these stand-alone action creators and the reducer with case reducers defined in the object passed to createSlice().

7a. Add an addTransaction property to the reducers object passed to createSlice().

const transactionsSlice = createSlice({
  name: 'transactions',
  initialState: initialState,
  reducers: {
    addTransaction: () => {},

  },
});

7b. Set addTransaction equal to a case reducer that receives two arguments—state and action. It should add the new transaction object (action.payload) to the correct category’s transaction list in the transactions state object.

const transactionsSlice = createSlice({
  name: 'transactions',
  initialState: initialState,
  reducers: {
    addTransaction: (state, action) => {
      // add the new transaction object (action.payload) to the correct category’s transaction list in the transactions state object.
      const category = action.payload.category;
      state[category].push(action.payload);
    },

  },
});

7c. Add a deleteTransaction property to the reducers object passed to createSlice().

const transactionsSlice = createSlice({
  name: 'transactions',
  initialState: initialState,
  reducers: {
    addTransaction: (state, action) => {
      // add the new transaction object (action.payload) to the correct category’s transaction list in the transactions state object.
      const category = action.payload.category;
      state[category].push(action.payload);
    },
    // Add a deleteTransaction property 
    deleteTransaction: () => {
      
    }
  },
});

7d. Set deleteTransaction equal to a case reducer that receives two arguments—state and action. It should delete the old transaction (action.payload) from the correct category’s transaction list in the transactions state object.

const transactionsSlice = createSlice({
  name: 'transactions',
  initialState: initialState,
  reducers: {
    addTransaction: (state, action) => {
      // add the new transaction object (action.payload) to the correct category’s transaction list in the transactions state object.
      const category = action.payload.category;
      state[category].push(action.payload);
    },
    // Add a deleteTransaction property 
    deleteTransaction: (state, action) => {
      // In the deletedIndex in transactionsReducer, action.payload.category and action.payload.id are both used. 
      const id = action.payload.id;
      const category = action.payload.category;
      // It should delete the old transaction (action.payload) from the correct category’s transaction list in the transactions state object.
      // 1. Find the category in `state` that matches the `category` property on `action.payload`
      // 2.  Filter out the old transaction (the transaction with an `id` matching the `id` property on `action.payload`) from that category's transaction array.
      state[category] = state[category].filter(transaction => transaction.id !== id)

    }
  },
});

Delete your old code and clean up your exports.

8a. Delete the stand-alone addTransaction and deleteTransaction,

8b. Export the addTransaction and deleteTransaction action creators generated by createSlice()and stored in transactionsSlice.

export { addTransaction, deleteTransaction } from transactionsSlice.action;

8c. Delete the stand-alone transactionsReducer,

8d. Update the export default statement to export the reducer generated by createSlice() and stored in transactionsSlice.

export default transactionsSlice.reducer;
1 Like