Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

redux-toolkit sharing state between reducer

I building small budget calculator and its the first time i am using redux-toolkit, the problem is How can share/pass state between reducers in redux-toolkit ? (how can use the totalIncomes and totalExpenses in the balance slice to calculate the total balance ?

another question is is ok to use redux-toolkit instead of plain redux

incomes.js :

const incomesSlice = createSlice({
  name: "incomes",
  initialState: {
    list: [],
    loading: false,
    totalIncomes: 0,
    lastFetch: null,
  },
  reducers: {
    ADD_INCOME: (state, action) => {
      state.list.push({
        id: uuidv4(),
        description: action.payload.description,
        amount: action.payload.amount,
      });
    },
    REMOVE_INCOME: (state, action) => {
      const index = state.list.findIndex(
        (income) => income.id === action.payload.id
      );
      state.list.splice(index, 1);
    },
    TOTAL_INCOMES: (state, action) => {
      state.totalIncomes = state.list.reduce(
        (acc, curr) => acc + curr.amount,
        0
      );
    },
  },
});

expenses.js :

const expensesSlice = createSlice({
  name: "expenses",
  initialState: {
    list: [],
    loading: false,
    totalExpenses: 0,
    lastFetch: null,
  },
  reducers: {
    ADD_EXPENSE: (state, action) => {
      state.list.push({
        id: uuidv4(),
        description: action.payload.description,
        amount: action.payload.amount,
      });
    },
    REMOVE_EXPENSE: (state, action) => {
      const index = state.list.findIndex(
        (expense) => expense.id === action.payload.id
      );
      state.list.splice(index, 1);
    },
    TOTAL_EXPENSES: (state, action) => {
      state.totalExpenses = state.list.reduce(
        (acc, curr) => acc + curr.amount,
        0
      );
    },
  },
});

export const {
  ADD_EXPENSE,
  REMOVE_EXPENSE,
  TOTAL_EXPENSES,
} = expensesSlice.actions;
export default expensesSlice.reducer;

balance.js :

const balanceSlice = createSlice({
  name: "balance",
  initialState: {
    total: 0
  },
  reducers: {
    CALC_TOTAL: (state, action) => {
      // How to Calculate this ?
    },
  },
});enter code here

export const { CALC_TOTAL } = balanceSlice.actions;
export default balanceSlice.reducer;
like image 812
ezra Avatar asked Mar 03 '23 13:03

ezra


2 Answers

For anyone looking into this - author's is the wrong approach to using redux for state management.

When using redux you want your state as normalized as possible - you shouldn't store uneeded/duplicated state or state that can be calculated based on other state, in this example there's no need to save totalIncomes since we can calculate this based on the list of incomes (same goes for totalExpenses and balance).

As mentioned, the totalIncomes shouldn't be part of the state but should be a calculated value, you can either calculate it on the fly or use a selector. In the below example I'll use a selector.

Redux Toolkit solution

To use it with Redux toolkit it might look something like this, I've removed parts of code for brewity:

incomes slice

// ...

const incomesSlice = createSlice({
  name: "incomes",
  initialState: {
    list: [],
  },
  reducers: {
    ADD_INCOME: (state, action) => {
      state.list.push({
        id: uuidv4(),
        description: action.payload.description,
        amount: action.payload.amount,
      });
    },
    REMOVE_INCOME: (state, action) => {
      const index = state.list.findIndex(
        (income) => income.id === action.payload.id
      );
      state.list.splice(index, 1);
    },
  },
});


export const getTotalIncome = createSelector(
    totalIncomeSelector,
    calculateTotalIncome,
);

export function totalIncomeSelector(state) {
    return state.incomes.list;
}

export function calculateTotalIncome(incomesList) {
    return incomesList.reduce((total, income) => total + income.amount);    
}

export const {
    ADD_INVOICE,
    REMOVE_INVOICE,
} = incomesSlice.actions;

export default incomesSlice.reducer;

expenses slice - removed parts for brewity

// ...

const expensesSlice = createSlice({
  name: "expenses",
  initialState: {
    list: [],
  },
  reducers: {
    ADD_EXPENSE: (state, action) => {
      state.list.push({
        id: uuidv4(),
        description: action.payload.description,
        amount: action.payload.amount,
      });
    },
    REMOVE_EXPENSE: (state, action) => {
      const index = state.list.findIndex(
        (income) => income.id === action.payload.id
      );
      state.list.splice(index, 1);
    },
  },
});


export const getTotalExpense = createSelector(
    totalExpenseSelector,
    calculateTotalExpense,
);

export function totalExpenseSelector(state) {
    return state.expenses.list;
}

export function calculateTotalExpenseexpenseList) {
    return expensesList.reduce((total, expense) => total + expense.amount); 
}

export const {
    ADD_EXPENSE,
    REMOVE_EXPENSE,
} = expensesSlice.actions;

export default expensesSlice.reducer;

balance slice - you don't really need a slice here, you just need a selector

import { getTotalIncome, totalIncomeSelector } from './incomeSlice';
import { getTotalExpense, totalExpenseSelector } from './expenseSlice';

export const getBalance = createSelector(
    getTotalIncome,
    getTotalExpense,
    (totalIncome, totalExpense) => totalIncome - totalIncome,
);


Example component

// ...

function BalanceComponent({
    totalIncome,
    totalExpense,
    balance,
}) {
    return (
        <div>
            <h1>Finance overview</h1>
            <div>
                <span>Total Income:</span>
                <span>{totalIncome}</span>
            </div>
            <div>
                <span>Total Expense:</span>
                <span>{totalExpense}</span>
            </div>
            <div>
                <span>Balance:</span>
                <span>{balance}</span>
            </div>
        </div>
    );
}

function mapStateToProps(state) {
    return {
        totalIncome: getTotalIncome(state),
        totalExpense: getTotalExpense(state),
        balance: getBalance(state),
    }
}

export default connect(mapStateToProps)(BalanceComponent);

Note: In the question the author seems to be breaking up his state into too many slices, all this can be a lot simpler by having it all as a single slice. That's what I would do.

like image 104
Davor Badrov Avatar answered Mar 07 '23 16:03

Davor Badrov


Is it ok to use redux-toolkit instead of plain redux

YES. It was originally created to help address common concerns about Redux. See its purpose.

How can share/pass state between reducers in redux-toolkit?

  1. You can pass the used state parts to action.payload.
dispatch(CALC_TOTAL(totalIncomes,totalExpenses))
  1. You can use extraReducers and "listen" to to your incomes/expenses changes.

  2. You can create a middleware or use createAsyncThunk where you can reference the most updated state with getState().

    • Toolkit docs.
like image 32
Dennis Vash Avatar answered Mar 07 '23 14:03

Dennis Vash