How can I delet a single element from the array in redux state using redux toolkit - redux

I am adding and deleting items in an array using the createSlice() function from the redux-toolkit library.
The addProject reducer function works fine but the removeRpoject doesn't work.
//projects.js
import { createSlice } from "#reduxjs/toolkit";
let lastId = 0;
const slice = createSlice({
name: "projects",
initialState: [],
reducers: {
projectAdded: (projects, action) => {
projects.push({
id: ++lastId,
name: action.payload.name,
});
},
projectRemoved: (projects, action) =>
(projects = projects.filter((pro) => pro.id !== action.payload.id)),
},
});
export const { projectAdded, projectRemoved } = slice.actions;
export default slice.reducer;
//store.js
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./projects";
const store = configureStore({ reducer: reducer });
export default store;
//index.js
import store from "./store/store";
import { projectAdded, projectRemoved } from "./store/projects";
const unsubscribe = store.subscribe(() => {
console.log("store Changed", store.getState());
});
store.dispatch(projectAdded({ name: "Project 1" }));
store.dispatch(projectRemoved({ id: 1 }));

You are replacing the root object of your state (projects) - this kills the immer change detection.
The simplest way is to just return the filtered array without assigning it to the draft object first:
projectRemoved: (projects, action) => projects.filter((pro) => pro.id !== action.payload.id),
See also:
https://immerjs.github.io/immer/docs/update-patterns#array-mutations

Related

How to add cases in ExtraReducer to match the actions created in currentReducer using createSlice() from #reduxjs/toolkit

Here below I have mentioned a redux slice. A fetchAllApps thunk function is created with createAsyncThunk for action 'allApps/allappsAdded/' which I dynamically got by allAppsAdded.type. When the fetchAllapps is dispatched it generated actions of type 'allApps/allappsAdded/pending', 'allApps/allappsAdded/fulfilled', 'allApps/allappsAdded/rejected' which I need to add in extraReducers to handle it by doing hardcode.Is there any way to add these action types like allAppsAdded.type programatically?. so that in future It makes easy for me to change these without redundant..
import {
configureStore,
createAsyncThunk,
createSlice
} from "#reduxjs/toolkit";
const initialState = {
apps: [],
categories: [],
loading: {
apps: false
}
};
const allappsSlice = createSlice({
name: "allapps",
initialState,
reducers: {
allappsAdded: (state, action) => {
state["apps"] = action.payload.apps;
state["categories"] = action.payload.categories;
}
},
extraReducers: {
}
});
export default () =>
configureStore({
reducer: allappsSlice.reducer
});
const { allappsAdded } = allappsSlice.actions;
const fetchAllApps = createAsyncThunk(allappsAdded.type, async () => {
console.log("ss");
setTimeout(() => ({ apps: [], categories: [] }), 2000);
});
export { allappsAdded, fetchAllApps };

redux "undefined" when trying to get the data

im trying to read some data from Redux store but I keep getting undefined
this is based on redux template created using:
create-react-app [appname] --template redux
I know there most be an issue in how I read the data
this is my store:
import { configureStore } from "#reduxjs/toolkit";
import calculatorReducer from "../features/Data_calculator_slice";
export const store = configureStore({
reducer: {
calculator: calculatorReducer,
},
});
my actions and initialState:
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
value: [{ name: "test", wight: 18, grade: 12, gpa: "A" }],
};
export const counterSlice = createSlice({
name: "data",
initialState,
reducers: {
incrementByAmount: (state, action) => {
state.value += action.payload;
const currentValue = selectCount();
console.log(currentValue);
},
},
});
export const { incrementByAmount } = counterSlice.actions;
export const selectCount = (state) => state.calculator.data;
/* export const selectCount = (state) => state.counter.value;
*/
export default counterSlice.reducer;
but here when I'm trying to fetch the data using the code below it returns undefined
const count = useSelector(selectCount);
console.log(count);
You cannot use the same selector in your reducers as you do in your component. One is scoped to the slice you're defining, the other is global. Also in your reducer you aren't passing the state to the selectCount selector so it can't return anything from the state.
in your reducer the selector for your value is (state) => state.value
in your component the selector for your value is (state) => state.calculator.value

How to set two separate States with Redux Toolkit?

I'm trying to establish two separate states with Redux Toolkit, one called posts and another called countTest. However, at the moment the two states share the same value.
posts is set to display a value of [] and countTest is set to display a value of 0. How do I differentiate the two states to display their unique value?
My actions file
import { createSlice } from "#reduxjs/toolkit";
import { database, auth } from "../firebase";
import { ref, set } from "firebase/database";
export const counterSlice = createSlice({
name: "posts",
initialState: {
value: [],
},
reducers: {
createAccount: (state, action) => {
const user = auth.currentUser;
const uid = user.uid;
set(ref(database, `users/${uid}`), {
email: action.payload.email,
name: action.payload.name,
});
},
},
});
export const testSlice = createSlice({
name: "countTest",
initialState: { value: 0 },
reducers: {
incrementAmount: (state, action) => {
state.value = state.value + 1;
},
decrementAmount: (state, action) => {
state.value = state.value - 1;
},
},
});
export const { createAccount, countTest } = counterSlice.actions;
export default counterSlice.reducer;
My store file
import { configureStore } from "#reduxjs/toolkit";
import counterReducer from "./actions";
export const store = configureStore({
reducer: {
posts: counterReducer,
countTest: counterReducer,
},
});
I know in my store file I'm using counterReducer without specifically referring to the actions createAccount and countTest. How do I go about retrieving the unique values of each and displaying in store? Do I need to create a separate file for each action (is this best practice?) instead of having all the actions in one file?
Thank you for any help
You need to use a createAction (on an another file or on one of your slice import action from it) and use extraReducer.
Doc :
https://redux-toolkit.js.org/api/createSlice (the last exemple)
exemple create action:
export const incrementAll = createAction("incrementAll");
use on your slices :
reducers : {//what you have for other things},
extraReducers: (builder) => {
builder.addCase(incrementAll, (state) => {
state.value++;
});
See the doc for more information or comment here if you need more explanation

Redux dispatch fires with correct payload, but state is not updated

I'm building a game using react-redux and TypeScript. I've got TS and React down, but redux... less so.
When the player clicks the start game button, the main menu is hidden via manipulation of redux state and the "gameState" is generated. "gameState" contains all the relevant information for the game world and entities therein, and is several thousand lines of serializable JSON. This part is definitely working, the problem comes when I try to dispatch to update it. I can see from the Redux browser extension that the payload being sent to the reducer function updateGameState is correct, but after the dispatch has been completed it's as if it never happened.
My question is simple: what am I doing wrong?
The code for the previously-mentioned dispatch is:
let nGS = gameStateGenerator.create(scene)
dispatch(updateGameState(nGS))
The layout of this part of the redux logic is as shown below. The four children of multiverse, universes, species, connections, and players, should all be populated, but are not.
I'm using combined reducers, as follows. I've not used ES6 notation for the reducer object properties as part of my attempts to rule out causes (which hopefully speaks to my level of desperation).
store.ts (top level)
import { configureStore, ThunkAction, Action } from '#reduxjs/toolkit';
import gameState from '../features/gameState/gameState';
export const store = configureStore({
reducer: {
gameState: gameState
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
gameState.ts (first and so far only child of root)
import { combineReducers, createSlice, PayloadAction } from '#reduxjs/toolkit';
import { GameState } from '../../interfaces/GameState';
import flags, { initialState as flagsInitialState} from '../flags/flags';
import multiverse, { initialState as multiverseInitialState } from '../multiverse/multiverse';
export const initialState: GameState = {
multiverse: multiverseInitialState,
flags: flagsInitialState
};
export const gameState = createSlice({
name: 'gameState',
initialState,
reducers: {
updateGameState: (state: GameState, action: PayloadAction<GameState>) => {
return Object.assign({}, state, action.payload)
}
}
});
export const { updateGameState } = gameState.actions;
export default combineReducers({
flags: flags,
multiverse: multiverse
})
flags.ts
import { createSlice } from '#reduxjs/toolkit';
import { FlagsState } from '../../interfaces/Flags';
export const initialState: FlagsState = {
ui: {
showMainMenu: true,
showWelcome: true,
gameStarted: false,
gameLoading: false,
gameLoaded: false
}
};
export const flags = createSlice({
name: 'flags',
initialState,
reducers: {
startGame: (state: Required<FlagsState>) => {
return Object.assign({}, state, {
ui: {
...state.ui,
showMainMenu: false,
gameStarted: true,
gameLoading: true
}
})
},
gameLoaded: (state: Required<FlagsState>) => {
return Object.assign({}, state, {
ui: {
...state.ui,
gameLoaded: true
}
})
}
}
});
export const { startGame, gameLoaded } = flags.actions;
export const getState = (state: FlagsState) => state;
export default flags.reducer;
And finally multiverse.ts
import { createSlice, PayloadAction } from '#reduxjs/toolkit';
import { Multiverse } from '../../interfaces/Multiverse';
import { Universe } from './../../interfaces/Universes';
export const initialState: Multiverse = {
universes: [],
species: [],
connections: [],
players: []
};
export const multiverse = createSlice({
name: 'multiverse',
initialState,
reducers: {
setUniverses: (state: Required<Multiverse>, action: PayloadAction<Universe[]>) => {
return Object.assign({}, state, { universes: action.payload })
}
}
});
export const { setUniverses } = multiverse.actions;
export const getState = (state: Multiverse) => state;
export default multiverse.reducer;
I'm think the trouble comes from your reducer, you use Object assign and return.
Redux-Toolkit uses Immer to change the state with no mutating like this :
(state, action) => state.value = action.payload
See the doc, https://redux-toolkit.js.org/usage/immer-reducers#immutable-updates-with-immer
So for you, you can do something like :
state.univers = {...state.univers, ... action.payload}
with no return.

How do I use redux toolkit's createSlice to create localStorage?

I am making a cart functionality using redux toolkit's createSlice. But in the browser's application tab, the value of the localStorage is showing as [object Object]. Can someone help me with this please?
cartSlice.js
import { createSlice } from '#reduxjs/toolkit';
import axios from 'axios'
const cartItemsFromStorage = localStorage.getItem('cartItems') ? localStorage.getItem('carts') : []
export const cartSlice = createSlice({
name: 'cart',
initialState: {
cartItems: cartItemsFromStorage,
},
reducers: {
add: (state, action) => {
const item = action.payload
const existItem = state.cartItems.find(x => x.product === item.product)
if (existItem) {
const currentItems = state.cartItems.map(x => x.product === existItem.product ? item : x)
state.cartItems = [...currentItems]
} else {
state.cartItems = [...state.cartItems, item]
localStorage.setItem('cartItems', state.cartItems)
}
},
// remove: (state, action) => {
// },
},
});
const { add } = cartSlice.actions;
export const selectCartItems = state => state.cart.cartItems;
export const addToCart = (id, qty) => async (dispatch) => {
const { data } = await axios.get(`/api/products/${id}`)
dispatch(add({
product: data._id,
name: data.name,
imgae: data.imgae,
price: data.price,
countInStock: data.countInStock,
qty
}))
}
export default cartSlice.reducer;
CartScreen.js
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { selectCartItems, addToCart } from '../features/cartSlice'
const CartScreen = ({ match, location, history }) => {
const productId = match.params.id
const qty = location.search ? Number(location.search.split('=')[1]) : 1
const dispatch = useDispatch()
const cartItems = useSelector(selectCartItems)
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty))
}
console.log(`cartItems: ${cartItems}`)
}, [dispatch, productId, qty])
return (
<div>
Cart
</div>
)
}
export default CartScreen
The next time I refresh the page, the initialState is not there, insted it shows [object Object]. I know the problem is with localStorage. Please correct me.
I think the problem is you are doing localStorage stuff in a reducer action. Reducer only can do simple operations and modify the state, so I encourage you to try to pass localStorage calls into the thunk action.

Resources