Both updateUiActive and updateData return new copy of state.
I am using updateData(updateUiActive(state, action), action) to return a new version of state which contain first the properties updated with updateUiActive and after with updateData.
I would like to know if there is a more elegant and efficient way to do so.
Maybe using JS carry?
const updateUiActive = (state, action) => {
return dotProp.set(state, 'navigation.ui.active', action.payload)
}
const updateData = (state, action) => {
const updatedData = state.navigation.data.map((x) => {
x.isActive = x.id === action.payload
return x
})
return dotProp.set(state, 'navigation.data', updatedData)
}
function navigationReducer (state = initialState, action) {
switch (action.type) {
case types.SET_ACTIVE:
return updateData(updateUiActive(state, action), action)
default:
return state
}
}
first of all, redux relies heavily on the state being immutable. You should not modify it, but return a new modified copy of it.
One option is to do it yourself (the spread operator from ES is usually very helpful for this).
Another option is using immutability-helper. The syntax might be hard to get in the beginning, but for more complex state it helps you getting a better structure.
Related
I'm working on a React/Redux application and I got the following problem:
I got a reducer like this:
export default (state = initialState, action) => {
switch (action.type) {
case action.type === FILTER.LIST_TYPES:
return Object.keys(state);
default:
return state;
}
};
This is the action types object:
export const FILTER = {
LIST_TYPES: "FILTER/LIST_TYPES"
};
export const listFilterTypes = () => ({
type: FILTER.LIST_TYPES
});
Now, when I dispatch this code, I receive false on the equality check like this (because they are both a string, but not the same type):
console.log(action.type === FILTER.LIST_TYPES);
I could change this for example to ==, but ESLINT does not like that and it can have some funky behaviour.. Is there a better approach to compare these object strings?
EDIT:
They are both the same value when I console.log them:
action.type: "FILTER/LIST_TYPES"
FILTER.LIST_TYPES: "FILTER/LIST_TYPES"
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 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. :)
I have an app with a menu of items, and at some point a user may edit the values of the items. When the user does so, I create a copy of the item in a seperate state branch instead of changing the original menu items. So my reducer looks like this:
const menuReducer = (state = [], action) => {
switch (action.type) {
case ADD_ITEM:
return [...state, {id: action.itemId, propA: action.itemPropA, propB: action.itemPropB}]
}
}
const editingMenuItem = (state = {}, action) => {
switch (action.type) {
case SET_EDIT_ITEM:
return {id: action.id, propA: action.itemPropA, propB: action.itemPropB}
case EDIT_ITEM:
return {id: state.id, propA: action.itemPropA, propB: action.itemPropB}
}
}
Someone selects that they want to edit an item, and this causes the dispatchEditItem thunk to trigger and create a copy in the state tree:
const dispatchEditItemThunk = itemId => (dispatch, getState) => {
const item = _.find(getState().menu, ['id', itemId]);
dispatch(setEditItem(item.id, item.propA, item.propB))
}
Then when someone wants to edit a prop, the editingThunk is dispatched:
const editingThunk = (itemId, propName) => (dispatch, getState) => {
let activeItem = getState().editingMenuItem;
// someValue is generated here
activeItem[propName] = someValue
dispatch(editItem(activeItem.propA, activeItem.propB))
}
The problem with this is that when activeItem[propName] = someValue happens, this changes the value of the item contained in the menuReducer array. I'm assuming because everything is pass by reference, and all the references lead back to the original value in the menuReducer. However, this isn't the way I would expect this to work. My assumption would be that calling getState would return a deep copy of the state, and not allow for these kinds of accidental mutations.
Is this a bug? If it isn't, is there a preferred way of writing thunks that avoids this kind of situation? In my real use case, the structure of the props in the menuItem is very complex, and it is handy to create an activeItem in the thunk and mutate it's values before dispatching to the state tree. Is doing this bad?
That's not a bug and mutating state object is highly discouraged. You can create a deep copy of an object using Object.assign and JSON.stringify methods as described here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign (Examples section).
If Redux was to create a deep copy of state on each dispatch call it could be more secure but also much slower.
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