function posts(state = [], action) {
switch(action.type){
case 'INCREMENT_LIKES':
const i = action.index
return [
...state.slice(0,i),
{...state[i], likes: state[i].likes + 1},
...state.slice(i + 1),
]
default:
return state
}
}
export default posts
Above code worked but for me it's not so elegant, it look super confusing and not so flexible. What if my id is in number, I can't do slice(i + 1), anymore. I can use map with object assign to solve it but I'm looking for more alternative to do update property value of array of object using es6
An alternative would be to make use of update from npm package immutability-helper which is every easy to handle and understand even in case of deep nested states and
import update from `immutability-helper`
function posts(state = [], action) {
switch(action.type){
case 'INCREMENT_LIKES':
const i = action.index
return update(state, {
[i]: {
likes: {
$set: state[i].likes + 1
}
}
})
default:
return state
}
}
export default posts
Related
I am trying to insert an array questions into a state array at a certain index in my array, however it is not always getting the order correct. I am expecting something like this:
[[/*arr 0*/], [/*arr 1*/], [/*arr 2*/], [/*arr 3*/], ...]
But I keep getting something like this:
[[/*arr 0*/], [/*arr 2*/], [/*arr 1*/], [/*arr 3*/], ...]
I tried following this guide from the official Redux docs, but to no avail. My reducer is the following:
export const questions = (state = [], action) => {
switch (action.type){
case SET_QUESTIONS:
const {questions, index} = action.payload;
let newArray = state.slice()
newArray.splice(index, 0, questions);
return newArray
case RESET_QUESTIONS:
return [];
default:
return state;
}
};
What am I doing wrong?
EDIT:
I have been asked to show how the actions are called, so here is the snippet where the actions are called. This loops about 7 times or so, depending on the length necessary. These calls are asynchronous, but I don't think this should necessarily change how the reducer functions.
axios.post(`${process.env.REACT_APP_SERVER_ENDPOINT}/getQuestionnaireData`, data).then(res => {
store.dispatch(setQuestions(res.data, index));
resolve();
}).catch(err => {
store.dispatch(setError(true));
});
The dispatched action looks like this:
export const setQuestions = (questions, index) => ({
type: SET_QUESTIONS,
payload: {
questions,
index
}
})
EDIT 2:
Because there was no way around the way that the dispatch calls are made (can't force insertions to be in order), and unfortunately none of the responses I got were able to solve my problem, I opted for a different solution. I ended up changing my reducer to the following:
export const questions = (state = {}, action) => {
switch (action.type){
case SET_QUESTIONS:
const {questions, index} = action.payload;
//Retrieve the previously stored state
let newObj = {
...state,
}
//Create a new object at the step key if it doesn't exist
if (!newObj[index]) newObj[index] = {};
//Assign the value at the id key in the step object
newObj[index] = questions;
return newObj;
case RESET_QUESTIONS:
return {};
default:
return state;
}
};
From there, I just ended up using Lodash to iterate over the object like an array. This approach proved to be pretty reliable, so that's what I stuck with.
Thanks to everyone for their answers. I hope they work for someone else who might come across this problem later.
Actually, you are not using Spread operator so Use spread operator and you can read about spread operator from following this link
Try the following code
export const questions = (state = [], action) => {
switch (action.type){
case SET_QUESTIONS:
const {questions, index} = action.payload;
return [
...state.slice(0,index),
questions,
...state.slice(index)
]
case RESET_QUESTIONS:
return [];
default:
return state;
}
};
You need to take account of the occasions (including the first time SET_QUESTIONS is dispatched) when your state array has fewer items in it than the new index.
Bearing that in mind, I'd probably do something like this:
export const questions = (state = [], action) => {
switch (action.type) {
case SET_QUESTIONS:
const { questions, index } = action.payload
const stateCopy = [...state]
stateCopy[index] = payload
return stateCopy
case RESET_QUESTIONS:
return []
default:
return state
}
}
I can't get my reducer to return updated state.
The action (confirmed with debugger) is an array of objects - something like: [{name: "test"}, {name: "second_test"}]
I think something must be wrong with my spread operator, although I've also tried Object.assign() in the debugger and that seems to return the expected result.
My components is apparently just getting the default state. Here's my reducer code. Any help would be appreciated. Thank you!
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK':
return {...state, all: [...state.all, action.payload]}
case 'FETCH_DECKS':
debugger;
return {...state, all: action.payload}
default: return state
}
}
I had this very issue recently (exactly as you describe), and I resolved the problem by using the immutability-helper package which is listed here on the ReactJS docs.
This approach allows you to delegate the heavy lifting of state immutability and nested state updates. You should find this resolves your issue:
import update from 'immutability-helper';
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK': {
/* Add payload to nested state.all list */
return update(state, { all : { $push : [action.payload] }});
}
case 'FETCH_DECKS': {
/* Replace state.all with incoming payload */
return update(state, { all : { $set : action.payload }});
}
default: return state
}
}
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 am trying to learn how to use Immutable.js properly. I found this article: http://frontendinsights.com/immutablejs-with-redux-his-best-friend/.
It has a following code snippet:
const initialState = Immutable.Map({ counter: 0 });
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return state.set('counter', state.get('counter') + 1);
case 'DECREMENT':
return state.set('counter', state.get('counter') - 1);
default:
return state;
}
};
const store = createStore(reducer, initialState);
What confuses me is that for me it looks like the switch is returning Map as a state.
I tried the immutable with following:
var obj = {
lol: 'trol',
}
var immu = Immutable.Map(obj)
var immu2 = immu
immu2 = immu2.set('lol', 'trollz')
console.log(immu2) // Map {size: 1, _root: ArrayMapNode, __ownerID: undefined, __hash: undefined, __altered: false}
console.log(immu2.toObject()) // Object {lol: "tral"}
I thought that Redux states needs to always be objects.
So, what is Map() actually returning and why is it possible to set that as a Redux state?
It is possible, however some of the default middleware in RTK claims it to be 'non-serializable'...
More here: Getting an error "A non-serializable value was detected in the state" when using redux toolkit - but NOT with normal redux
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