I am trying to prevent duplicate messages from being pushed into the state. Below is my messagesReducer.
const initialState = List([]);
const messagesReducer = {
[actionTypes.ADD_NEW_RESPONSE_MESSAGE]: (state, { text }) => state.push(createNewMessage(text, MESSAGE_SENDER.RESPONSE)),
}
export default (state = initialState, action) => createReducer(messsagesReducer, state, action);
You can check the array for duplicates using the higher order function 'some'. Also currently your reducer returns the length of the array, because that is how .push works, which is probably not what you want.
In the code below I assume that the id of the message is saved in the attribute 'id' inside the message object.
const initialState = [];
const messagesReducer = {
[actionTypes.ADD_NEW_RESPONSE_MESSAGE]: (state, { text }) => {
if (!state.some(m => m.id === MESSAGE_SENDER.RESPONSE)) return [...state, (createNewMessage(text, MESSAGE_SENDER.RESPONSE))];
return state;
},
}
export default (state = initialState, action) => createReducer(messsagesReducer, state, action);
Considering you are working with a list and you want to check if Id exists before pushing,
i would the following approach:
//Returns a True or False depending on if it finds the item or not
let find_status = !!state.find(item => item.id === message.id)
find_status?state.push(message):state;
Related
everyone. I want to store an Object into an array using redux toolkit. Unfortunately, it is the case that a new object is not added, but simply replaced. How can I change this behavior ?
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
warenkorb: [],
preis: 0,
bewertung: 0,
};
export const produktSlice = createSlice({
name: "produkt",
initialState,
reducers: {
hinzufügen: (state, action) => {
// Also not work
// state.warenkorb.push(action.payload);
//
state.warenkorb.push([...state.warenkorb, action.payload]);
state.preis += action.payload.preis * action.payload.anzahl;
},
entfernen: (state, action) => {
if (state.warenkorb.includes(action.payload)) {
state.warenkorb.splice(
state.warenkorb.findIndex((id) => id === action.payload),
1
);
}
},
bewerten: (state, action) => {
state.bewertung = action.payload.bewertung;
},
},
});
export const { hinzufügen, entfernen, bewerten } = produktSlice.actions;
export default produktSlice.reducer;
As you can see, a new call is started each time with the new object
And if I look at it with the help of the debugger, I get to see this. Why the first two objects and why do they look like this?
You should not push into the state you should replace the current state with a whole new state to ensure state updates
The code should be:
hinzufügen: (state, action) => {
state.warenkorb = [...state.warenkorb, action.payload];
}
I made this sandbox for the total working example
I want to update Array
initialState
const initialState = { name:'a', arrNames:[1,2,3,4]}
reducer
const userRuducer = createReducer(initialState,(builder)=>{
builder.addCase('UPDATE_ARRAY',(state,action)=>{
// how to add ' 12' in the arraay
})
})
component
<Button onClick={() =>dispatch({type:'UPDATE_ARRAY',payload:12}) } variant='contained'>update Array</Button>
result:-
arrNames:[1,2,3,4, 12]
You are using redux-toolkits so it basically just push the new item to the object:
builder.addCase("UPDATE_ARRAY", (state, action) => {
state.arrNames.push(action.payload)
};
Because redux-toolkits already included with immerjs
builder.addCase("UPDATE_ARRAY", (state, action) => {
return {
...state,
arrNames:[...state.arrNames,action.payload]
};
})
here the state is immutable and conceded as better practice
I have the following code (I deleted most of it to make it easier to understand - but everything works):
"role" reducer:
// some async thunks
const rolesSlice = createSlice({
name: "role",
initialState,
reducers: { // some reducers here },
extraReducers: {
// a bunch of extraReducers
[deleteRole.fulfilled]: (state, { payload }) => {
state.roles = state.roles.filter((role) => role._id !== payload.id);
state.loading = false;
state.hasErrors = false;
},
},
});
export const rolesSelector = (state) => state.roles;
export default rolesSlice.reducer;
"scene" reducer:
// some async thunks
const scenesSlice = createSlice({
name: "scene",
initialState,
reducers: {},
extraReducers: {
[fetchScenes.fulfilled]: (state, { payload }) => {
state.scenes = payload.map((scene) => scene);
state.loading = false;
state.scenesFetched = true;
}
}
export const scenesSelector = (state) => state.scenes;
export default scenesSlice.reducer;
a component with a button and a handleDelete function:
// a react functional component
function handleDelete(role) {
// some confirmation code
dispatch(deleteRole(role._id));
}
My scene model (and store state) looks like this:
[
{
number: 1,
roles: [role1, role2]
},
{
number: 2,
roles: [role3, role5]
}
]
What I am trying to achieve is, when a role gets deleted, the state.scenes gets updated (map over the scenes and filter out every occurrence of the deleted role).
So my question is, how can I update the state without calling two different actions from my component (which is the recommended way for that?)
Thanks in advance!
You can use the extraReducers property of createSlice to respond to actions which are defined in another slice, or which are defined outside of the slice as they are here.
You want to iterate over every scene and remove the deleted role from the roles array. If you simply replace every single array with its filtered version that's the easiest to write. But it will cause unnecessary re-renders because some of the arrays that you are replacing are unchanged. Instead we can use .findIndex() and .splice(), similar to this example.
extraReducers: {
[fetchScenes.fulfilled]: (state, { payload }) => { ... }
[deleteRole.fulfilled]: (state, { payload }) => {
state.scenes.forEach( scene => {
// find the index of the deleted role id in an array of ids
const i = scene.roles.findIndex( id => id === payload.id );
// if the array contains the deleted role
if ( i !== -1 ) {
// remove one element starting from that position
scene.roles.splice( i, 1 )
}
})
}
}
So I have the following reducer
const objectType = (state = {type: 0, image:defaultImage, moreOptions: {tap: 0, griff: 0} },
action) => {....
case 'CHANG_OPTIONS':
return state = {...state, moreOptions: {tap:action.tap, griff: action.griff}}
This is the action, so I get a dynamic category and assign the id of the product.
export const changeOptions = (category, id) => {
return {
type: 'CHANG_OPTIONS',
[category]: id,
}
}
An example of dispatch would be
dispatch(changeOptions('tap', 0))
Now whenever I click on a tap or a griff, my object remove the other category from the list.
Here is a screenshot from the Redux Debugger tool
I'm sure that the problem is in my reducer:
moreOptions: {tap:action.tap, griff: action.griff} Is there a way I can spread the object and update only the one that was changed?
It's because you're overwritting both tap and griff value regardless of their input value. Try below.
const newOptions = {};
if (action.tap) {
newOptions.tap = action.tap;
}
if (action.griff) {
newOptions.griff = action.griff;
}
return (state = {
...state,
moreOptions: {
...state.moreOptions,
...newOptions
}
});
Hey everyone probably a simple question, basically I have a button when i click it fires an action and passes down the whole object that I concat to array if its not duplicate but strangely what happens because I save data to local-storage and after I load it from there it does not check for duplicate and duplicates the array item. My reducer code below maybe the error is there?
Searched as much as possible.
const initialState = {
favourites: []
};
const favourites = (state = initialState, action) => {
const { payload } = action;
switch (action.type) {
case actionTypes.ADD_FAVOURITES:
return {
...state,
favourites:
state.favourites.indexOf(payload) === -1
? state.favourites.concat(payload)
: state.favourites
};
default:
return state;
}
};
The issue here seems to be that state.favourites.indexOf(payload) === -1 is always true. This is because the function Array.prototype.findIndex() does not find identical objects.
You should use an alternate method of checking to see if the payload object is already in the favourites array. For example, you could try something like this:
const favourites = (state = initialState, action) => {
const { payload } = action;
switch (action.type) {
case actionTypes.ADD_FAVOURITES:
return {
...state,
favourites:
JSON.stringify(state.favourites).indexOf(JSON.stringify(payload)) === -1
? state.favourites.concat(payload)
: state.favourites
};
default:
return state;
}
};