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. :)
Related
I am using react with redux for my application, I have state as array of objects, in reducer for adding new element in an array the entire state is getting changed to 4 instead of showing new element along with old ones and also giving me error "todos.map is not a function".
My State
export const todos = [
{
id: "1",
item: "Buy Milk"
},
];
reducer
export const reducer = (state = todos, action) => {
let newTodos;
switch (action.type) {
case ADD_TODO:
newTodos = state;
newTodos = newTodos.push(action.payload);
return newTodos;
case DELETE_TODO:
break;
case UPDATE_TODO:
break;
default:
return state;
}
};
action.payload(ADD_TODO) in redux dev tool
type(pin): "ADD_TODO"
payload(pin)
id(pin): "870f7b60-5267-11eb-85d2-cbd5e33df548"
item(pin): "ghgh"
After dispatching action getting error "todos.map is not a function" and state is getting changed to
(pin): 4
push returns the length of the new array, not the array itself. Instead you need to create a new array without mutating the original:
case ADD_TODO:
return [...state, action.payload];
You mutate state and then assign state to the wrong value in add todoe:
[].push({}) === 1;
try this instead:
return [...state, action.payload]
In a react component that uses state hooks to expose several state properties, is there a way to iterate through all of the state properties and potentially change them? The issue is that I have lots of state properties, so I'd rather not hard-code all the getters and setters to iterate over the state properties.
In this example, let's say that all of my state properties have a default of 0, and if they are different, I'd like to do something. How do I loop over the state properties?
const exampleComponent = () => {
const [prop1, setProp1] = React.useState(0);
const [prop2, setProp2] = React.useState(0);
const [prop3, setProp3] = React.useState(0);
//...etc., lots of properties
// Loop over the properties. How should this loop be written?
Object.keys(this.state).map(function (key) {
// do something with each key-value pair here
});
An alternative is to assign the states that you want into an array and then destructure them into named constants (if required) and enumerate the states array. See example below:
const exampleComponent = () => {
const states = [React.useState(0), React.useState(0), React.useState(0)];
const [
[prop1, setProp1],
[prop2, setProp2],
[prop3, setProp3],
] = states;
// Loop over the properties.
states.forEach(([state, setState]) => {
// do something with each key-value pair here
});
}
If you need to loop over the properties, I'd use an array for state instead:
const [numArr, setNumArr] = useState([0, 0, 0]);
// ...
numArr.forEach((num, i) => {
// do something with each key-value pair here
});
If you have lots of states that are related to each other, then instead of having each state separately, you might be better off using the useReducer hook.
EDIT:
Apologies, I should have mentioned this earlier that handling state with useReducer hook can be a bit verbose and may be complex if one is not familiar with it.
Here is an example, where instead of having three separate states, we have one state object with three properties, when UPDATE_ACTION1 is dispatched, the code loops over the properties and all the relevant ones are incremented by 2.
//define some actions
const UPDATE_ACTION1 = "UPDATE_ACTION1";
const UPDATE_ACTION2 = "UPDATE_ACTION2";
//define a reducer function that will update the state
const objReducer = (state, action) => {
switch (action.type) {
case UPDATE_ACTION1:
const keys = Object.keys(state);
const newState = {};
keys.forEach(key => {
//perform any function on each property/key
//here we just increment the value of each property by the given value
if (key !== "isValid") {
newState[key] = state[key] + action.value;
}
});
return newState;
case UPDATE_ACTION2:
//do something else e.g. check validity and return updated state
return { ...state, isValid: true };
default:
return state;
}
};
//inside the component: call useReducer and pass it the reducer function and an initial state
//it will return the current state and a dispatch function
const [objState, dispatch] = useReducer(objReducer, {
prop1: 0,
prop2: 0,
prop3: 0
});
//somewhere in your code, dispatch the action. it will update the state depending upon the action.
const somethingHappens = () => {
//some other operations are performed here
dispatch({ type: UPDATE_ACTION1, value: 2 });
};
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
}
}
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.
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;
}
}