If an item is already in an array, I want to remove it, else I'd like to add it to the array. How should I go about this? I have attached incomplete code:
import { SELECTED_ITEM } from '../actions/type'
const INITIAL_STATE = []
export default(state = INITIAL_STATE ,action) => {
switch(action.type) {
case SELECTED_ITEM:
state.map(function(value) {
if(value === action.payload) {
?????
}
})
return [...state,action.payload]
default : return state;
}
}
Do I have to loop through all values to find the item? And how can I return the new state with removed or added item?
You can do an Array#includes check, then filter as needed (assuming primitive values):
if(state.includes(value)) {
return state.filter(value => value !== action.payload);
}
return [...state, action.payload];
I managed using lodash, which is best for array iteration. For using normal loops or every i find some issue in some cases. Thus its not reliable.
var e = _.findIndex(state.itemList, function(o) { return o.UserID== action.payload.UserID; });
if(e!==-1){
state.itemList.splice(e, 1)
return { ...state, itemList: [...state.itemList] }
}
Related
I have a StackBlitz that you can fork, I found a similar answer but I'm having trouble applying it to my example, I think it's because I have an array of objects.
I have the code working but it's very verbose, and I would like something easier to read that others who use Redux will recognize.
const initialState = [];
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO': {
return [...state, {
id: state.length,
name: action.name,
complete: false
}];
}
case 'TOGGLE_COMPLETE': {
let left = state.filter((_, index) => index < action.index)
let right = state.filter((_, index) => index > action.index)
let completed = state.filter((_, index) => index == action.index)
let updatedItem = {
id: completed[0].id,
name: completed[0].name,
complete: !completed[0].complete
}
return [...left, updatedItem, ...right];
}
case 'CLEAR': {
return initialState;
}
default: {
return state;
};
}
}
I feel like the answer is is similar to this one? Update array object in React Redux reducer
In the answer I cited above, his object has state.posts How can I update my example to be more like this one?
How can I target my Todos if I don't have a similar state object as I can't do something like:
state.todos.complete = false.
I want to write a better reducer for the 'COMPLETE' action. Do I need to modify how my state is structured, ie dopes it need to be an empty object instead of an array of objects?
Just map:
case 'TOGGLE_COMPLETE': {
return state.map((item, i) => i === action.index
? {...item, complete: !item.complete}
: item
)
}
You can conditionally update "complete" without iterating multiple times.
I have an object that trying too deeply clone it before mutate in Redux but the nested objects become empty after deep cloning it with lodash or json
const initial = {
infamy: {a: 1}
}
export const playerReducer = (state = initial, action) => {
switch (action.type) {
case SET_DATA:
console.log("III", state);
state = cloneDeep(state); //Why the neseted obj becomes empty?
console.log("JJJ", state);
break;
}
};
Edit:
looks look the issue was the condition i had for checking if the object was empty wasn't working so the empty data from the api was replacing the initial values but im wounding why the console.log was showing the post made mutation rather than pre made mutation
case SET_DATA:
console.log("III", state);
const nextState = cloneDeep(state);
console.log("JJJ", nextState); //why the log shows the change in line 10 made? shouldn't it log the values then change happen?
nextState.membershipId = action.payload.membershipId;
nextState.membershipType = action.payload.membershipType;
nextState.displayName = action.payload.displayName;
console.log(action.payload.gambitStats);
if (action.payload.gambitStats.allTime !== "undefined") { //the bug is here
nextState.gambitStats = cloneDeep(action.payload.gambitStats);
nextState.infamy = cloneDeep(action.payload.infamy);
}
return nextState;
You are checking for undefined as a string "undefined" instead of:
if (action.payload.gambitStats.allTime !== undefined) { ...
or just:
if (!!action.payload.gambitStats.allTime)
In principal I would say that the state would not be emptied by the use of cloneDeep alone.
In the other hand, I see that you are using a Redux pattern and you should not directly manipulate the state.
Instead you should return the next state and also return the current state by default.
const initial = {
infamy: {a: 1}
}
export const playerReducer = (state = initial, action) => {
switch (action.type) {
case SET_DATA:
const nextState = cloneDeep(state);
// Modify nextState according to the intent of your action
return nextState;
default:
return state;
}
};
Hope it helps. :)
In my basic understanding about the functional programming way and the way that I need generate the next state in redux when a specific array's change, i've been trying this approach for modify a todo item by index in a todo list:
const toggleSelectedTodo = (todos, selectedIndex) => {
return todos.map((todo, index) => (
{ ...todo, completed: index === selectedIndex ? !todo.completed : todo.completed }
))
}
export default function reducer(state = INITIAL_STATE, action) {
switch (action.type) {
...
case "TOGGLE_SELECTED":
return {
...state,
todos: toggleSelectedTodo(state.todos, action.index)
}
...
default:
return state
}
}
So, my idea it's that I need re-map the all the todo item objects maintaining their values except for the target item. But, I think that isn`t performant and I'm worry about the performance for large arrays and the worst case.
Do you know a better strategies for modify a object property inside an array or this it's the right way for do this?
You should only modify the object with selected index. There's no need to use the spread operator for EVERY object in the array. That can be the potential performance bottleneck. To avoid that your toggleSelectedTodo function should look like this:
const toggleSelectedTodo = (todos, selectedIndex) => {
return todos.map((todo, index) => {
if(index === selectedIndex) {
return { ...todo, completed: !todo.completed }
}
return todo
})
}
Besides that you shouldn't worry about the performance unless you're processing thousands of items.
I am pretty new guy to redux.I am little bit confused that how to change this data without mutating it.
my data structure like this
{
postOwnwerName:"",
postTags:[],
photos:[
{
id:dc5c5d7c5s,
caption:null
},
{
id:dccccccc5s,
caption:null
}
],
}
Whent this function fires
this.props.updateCaption(id,caption)
my Reducer should set data according to the id in the data structure without mutating it
import * as types from '../Actions/actionTypes';
const initialState = {
postOwnwerName:"",
postTags:[],
photos:[],
};
export default function uploadReducer(state=initialState,action) {
switch (action.type){
case types.SETPHOTOSTATE:
return Object.assign({},state,{ photos:state.photos.concat(action.payload) });
case types.SETCAPTIONTEXT:
//this method is not working
return (
Object.assign({},state,state.photos.map((data) => {
if(data.id == action.payload.id){
return Object.assign({},data,{caption:action.payload.caption})
}else{
return data
}
}))
)
default:
return state
}
}
if the state object has many level it will be difficult to change state without mutating. In those scenario I first copy the state deeply and then make changes to the new state and return new state. See here Cloning object deeply. You can try this once.
export default function uploadReducer(state=initialState,action) {
let newState = JSON.parse(JSON.stringify(state));
switch (action.type){
case types.SETPHOTOSTATE:
newState.photos.push(action.payload);
return newState;
case types.SETCAPTIONTEXT:
newState.photos.map((data) => {
if(data.id == action.payload.id){
data.caption = action.payload.caption;
}
});
return newState;
default:
return newState;
}
}
I know I'm not supposed to mutate the input and should clone the object to mutate it. I was following the convention used on a redux starter project which used:
ADD_ITEM: (state, action) => ({
...state,
items: [...state.items, action.payload.value],
lastUpdated: action.payload.date
})
for adding an item - I get the use of spread to append the item in the array.
for deleting I used:
DELETE_ITEM: (state, action) => ({
...state,
items: [...state.items.splice(0, action.payload), ...state.items.splice(1)],
lastUpdated: Date.now()
})
but this is mutating the input state object - is this forbidden even though I am returning a new object?
No. Never mutate your state.
Even though you're returning a new object, you're still polluting the old object, which you never want to do. This makes it problematic when doing comparisons between the old and the new state. For instance in shouldComponentUpdate which react-redux uses under the hood. It also makes time travel impossible (i.e. undo and redo).
Instead, use immutable methods. Always use Array#slice and never Array#splice.
I assume from your code that action.payload is the index of the item being removed. A better way would be as follows:
items: [
...state.items.slice(0, action.payload),
...state.items.slice(action.payload + 1)
],
You can use the array filter method to remove a specific element from an array without mutating the original state.
return state.filter(element => element !== action.payload);
In the context of your code, it would look something like this:
DELETE_ITEM: (state, action) => ({
...state,
items: state.items.filter(item => item !== action.payload),
lastUpdated: Date.now()
})
The ES6 Array.prototype.filter method returns a new array with the items that match the criteria. Therefore, in the context of the original question, this would be:
DELETE_ITEM: (state, action) => ({
...state,
items: state.items.filter(item => action.payload !== item),
lastUpdated: Date.now()
})
Another one variation of the immutable "DELETED" reducer for the array with objects:
const index = state.map(item => item.name).indexOf(action.name);
const stateTemp = [
...state.slice(0, index),
...state.slice(index + 1)
];
return stateTemp;
Deleting an item using redux in different ways.
Method 1: In that case is used createSlice( .. )
const { id } = action.payload; // destruct id
removeCart: (state, action) =>{
let { id } = action.payload;
let arr = state.carts.filter(item => item.id !== parseInt(id))
state.carts = arr;
}
Method 2: In that case is used switch (... ), spread-operator
const { id } = action.payload; // destruct id
case actionTypes.DELETE_CART:
return {
...state,
carts: state.carts.filter((item) => item.id !== payload)
};
For both methods initialized this state:
initialState: {
carts: ProductData, // in productData mocked somedata
}
The golden rule is that we do not return a mutated state, but rather a new state. Depending on the type of your action, you might need to update your state tree in various forms when it hits the reducer.
In this scenario we are trying to remove an item from a state property.
This brings us to the concept of Redux’s immutable update (or data modification) patterns. Immutability is key because we never want to directly change a value in the state tree, but rather always make a copy and return a new value based on the old value.
Here is an example of how to delete a nested object:
// ducks/outfits (Parent)
// types
export const NAME = `#outfitsData`;
export const REMOVE_FILTER = `${NAME}/REMOVE_FILTER`;
// initialization
const initialState = {
isInitiallyLoaded: false,
outfits: ['Outfit.1', 'Outfit.2'],
filters: {
brand: [],
colour: [],
},
error: '',
};
// action creators
export function removeFilter({ field, index }) {
return {
type: REMOVE_FILTER,
field,
index,
};
}
export default function reducer(state = initialState, action = {}) {
sswitch (action.type) {
case REMOVE_FILTER:
return {
...state,
filters: {
...state.filters,
[action.field]: [...state.filters[action.field]]
.filter((x, index) => index !== action.index)
},
};
default:
return state;
}
}
To understand this better, make sure to check out this article: https://medium.com/better-programming/deleting-an-item-in-a-nested-redux-state-3de0cb3943da