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;
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.
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
?
action.payload
.dispatch(CALC_TOTAL(totalIncomes,totalExpenses))
You can use extraReducers
and "listen" to to your incomes/expenses changes.
You can create a middleware or use createAsyncThunk
where you can reference the most updated state with getState()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With