I have the following code to update the currentScore of a rubricItem object. This works fine.
case SAVE_SCORELIST_SUCCESS:
const scoreItem = action.payload.scoreItem;
return {
...state,
loading: false,
editing: false,
rubricItems: {
...state.rubricItems,
[scoreItem.rubricItemId]: {
...state.rubricItems[scoreItem.rubricItemId],
currentScore: scoreItem.currentScore,
}
}
};
However, I may receive an array object holding scores for multiple rubricItems instead of updating a single rubricItem with a single scorItem as I did above.
I know I can use .map() to iterate through the array:
scoreItems.map(si=>{})
But, I do not know how I can integrate it into this:
case SAVE_SCORELIST_SUCCESS:
const scoreItems = action.payload.scoreItems;
return {
...state,
loading: false,
editing: false,
rubricItems: {
...state.rubricItems,
[scoreItems[x].rubricItemId]: {
...state.rubricItems[scoreItems[x].rubricItemId],
currentScore: scoreItems[x].currentScore,
}
}
};
Any ideas?
You can try this:
First you need to iterate over scoreItems and make a map object of updated score items.
Once you have done that, you can use the spread operator with the current score items in state.
case SAVE_SCORELIST_SUCCESS:
let updatedScoreItems = {};
action.payload.scoreItem.forEach(scoreitem => {
updatedScoreItems[scoreItem.rubricItemId] = {
...state.rubricItems[scoreItem.rubricItemId],
currentScore: scoreItem.currentScore,
}
})
return {
...state,
loading: false,
editing: false,
rubricItems: {
...state.rubricItems,
...updatedScoreItems
}
};
Instead of mapping over scoreItem, map over the rubricItems which will be cleaner.
const updatedRubricItems = items.rubricItems.map(rubricItem => {
const scoreForRubric = scoreItems.find(si => si.rubricItemId === rubricItem.id);// i assume you have some id for your rubric item
if(scoreForRubric){
return {...rubricItem, currentScore: scoreForRubric.currentScore}
}else {
return rubricItem
}
});
return {
...state,
loading: false,
editing: false,
rubricItems: updatedRubricItems
};
Related
I build an app in React with Redux and in my state I have a list of objects and I want to update one object from that list by unique id.
My object looks like:
{
id: '',
title: '',
description: '',
label: '',
}
My state:
const initialState = {
compare: dayjs().month(),
savedEvents: [],
}
When I push a new event in that list I use:
case 'events/setNewEvent':
return { ...state, savedEvents: [...state.savedEvents, action.payload] };
My problem is that I don't know to write the right code to update just one object by id sent from my form.
You can use combination of Array method map and spread operator
function updateOne(array, obj) {
return array.map((item) => {
if (obj.id === item.id) {
// update whatever you want
return {...item, title: obj.title };
} else {
return item;
}
})
}
reducer:
case 'events/setNewEvent':
return {
...state,
savedEvents: updateOne(state.savedEvents, action.payload)
};
I have the following reducer in React Redux:
export const reducer = (state = initialStateData, action) => {
switch (action.type) {
case Action.TOGGLE_ARR_FILTER:
{
const subArr = state.jobOffers.filters[action.key];
const filterIdx = subArr.indexOf(action.value[0]);
const newArr = { ...state.jobOffers.filters
};
if (filterIdx !== -1) {
newArr[action.key].splice(filterIdx, 1);
} else {
newArr[action.key].push(action.value[0]);
}
return {
...state,
jobOffers: {
...state.jobOffers,
filters: {
...newArr,
},
},
};
}
And this is my object:
const initialStateData = {
jobOffers: {
filters: {
employments: [],
careerLevels: [],
jobTypeProfiles: [],
cities: [],
countries: [],
},
configs: {
searchTerm: '',
currentPage: 1,
pageSize: 5,
},
},
};
The reducer as such seems to work, it toggles the values correctly.
But: Redux always shows "states are equal", which is bad as it won't recognize changes.
Can someone help ? I assume that I am returning a new object..
you can use Immer , redux also uses this for immutable updates for nested stuffs.
Because of this, you can write reducers that appear to "mutate" state, but the updates are actually applied immutably.
const initialStateData = {
jobOffers: {
filters: {
employments: [],
careerLevels: [],
jobTypeProfiles: [],
cities: [],
countries: [],
},
configs: {
searchTerm: '',
currentPage: 1,
pageSize: 5,
},
},
};
export const reducer = immer.produce((state = initialStateData, action) => {
switch (action.type) {
case Action.TOGGLE_ARR_FILTER:
const subArr = state.jobOffers.filters[action.key];
const filterIdx = subArr.indexOf(action.value[0]);
const newArr = state.jobOffers.filters;
if (filterIdx !== -1)
newArr[action.key].splice(filterIdx, 1);
else
newArr[action.key].push(action.value[0]);
return state;
}
})
Although you take a copy of state.jobOffers.filters, this still holds references to original child arrays like employments. So when you mutate newArr[action.key] with splice or push, Redux will not see that change, as it is still the same array reference.
You could replace this:
if (filterIdx !== -1) {
newArr[action.key].splice(filterIdx, 1);
} else {
newArr[action.key].push(action.value[0]);
}
with:
newArr[action.key] = filterIdx !== -1
? [...newArr[action.key].slice(0, filterIdx), ...newArr[action.key].slice(filterIdx+1)]
: [...newArr[action.key], action.value[0]]);
BTW, you don't have to copy newArr again, as it already is a copy of filters. You can replace:
filters: {
...newArr,
},
by just:
filters: newArr,
I am building an app. This app needs to request a woocommerce API for product data. The API will only allow 100 items at a time, so I have had to call this request 4 times with dynamic page parameters.
My aim for this data is to have a reducer combine the data into one array that can be filtered in a react component.
My problem at the moment is that my reducer is adding each API call to state in its own array. So rather than have a big array with 300 ish products in, I have 1 array that contains 4 arrays each with 100 products in. Please see the image below.
State data result
Here is the action:
export function fetchJuiceData(page) {
return dispatch => {
dispatch(getDataPending("juicedata"));
return axios
.get(
"API_call/end_point/&page=" +
page
)
.then(response => {
dispatch(getDataSuccess("juicedata", response));
})
.catch(err => {
dispatch(getDataFailure("juicedata", err));
});
};
}
Which gets run 4 times with async:
const juiceDataPages = 4;
var i;
for (i = 0; i < juiceDataPages; i++) {
await dispatch(fetchJuiceData(i + 1));
}
My reducer:
const juiceDataReducer = (state = initState, action) => {
switch (action.type) {
case "FETCH_JUICEDATA_PENDING": {
return { ...state, fetching: true };
}
case "FETCH_JUICEDATA_REJECTED": {
return { ...state, fetching: false, error: action.payload };
}
case "FETCH_JUICEDATA_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
juiceData: [...state.juiceData, action.payload]
};
}
default: {
return state;
}
}
};
I am not the greatest coder in the world and would love your input. I have been banging my head against a wall for days now. TIA.
It seems like the payload you're putting in is an array by itself. So try using the spread operator on it too like so:
case "FETCH_JUICEDATA_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
juiceData: [...state.juiceData, ...action.payload]
};
}
This way only the items inside of the payload are added instead of the whole array.
Consider the following state:
const initState = {
id: {
data: null,
isFetching: false,
fetchingError: null
},
bookmarks: {
IDs: {
news: [],
opps: [],
posts: []
},
data: {
news: [],
opps: [],
posts: []
},
isFetching: false,
fetchingError: null
},
role: null,
membership: null,
}
How do I update just the posts array in the ÌDs array in the bookmarks array? I tried this:
case 'SET_USER_BOOKMARKED_POSTS':
return {
...state,
bookmarks: {
IDs: {
posts: action.payload
}
}
}
But when I log the state to the console the IDs array then only contains posts, while the opps and news arrays are not there anymore.
You need to destruct all inner structure:
case 'SET_USER_BOOKMARKED_POSTS':
return {
...state,
bookmarks: {
...state.bookmarks,
IDs: {
...state.bookmarks.IDs,
posts: action.payload
}
}
}
But this is not very convinient, better use lodash merge
You need use the spread operator for state.bookmarks.IDs also as when specifies keys after the spread operator, the value for the keys are overwritten.
case 'SET_USER_BOOKMARKED_POSTS':
return {
...state,
bookmarks: {
...state.bookmarks
IDs: {
...state.bookmarks.IDs,
posts: action.payload
},
}
}
The way we do it is a non-mutative method using Object.assign to basically point and update a field in the redux state. Something like:
return Object.assign({}, state.bookmarks, {
IDs: action.payload,
});
This will make sure you're not mutating the state, and returns the new bookmarks. You can adjust this to return the entire state if you'd like, it depends on if you need the other data on the update.
I'm creating a new action on a Redux store.
I have a list of items which all have a property of "overlayVis". I want to set all of these to true except the specified Id. My current implementation is
case ITEM_OVERLAY_TOGGLE:
// object to be updated and returned
var returnObj = state.data;
state.data.map((item) => {
if (item.id === action.id) {
returnObj = Object.assign({}, ...state, {
data: [
...state.data.slice(0, item.id),
Object.assign({}, ...state.data[item.id], {overlayVis: false}),
...state.data.slice(item.id + 1)]
});
} else if (!item.overlayVis) {
returnObj = Object.assign({}, ...state, {
data: [
...state.data.slice(0, item.id),
Object.assign({}, ...state.data[item.id], {overlayVis: true}),
...state.data.slice(item.id + 1)]
});
}
});
return returnObj;
Each iteration overrides the previous iteration so only one change is made each time the action is run. I have attempted to use "state" and "returnObj " instead of "...state" but it has not worked. I've not posted on here in a long time but I'm am out of ideas.
Any help will be greatly appreciated!
I think your map function can be changed to look like this. I can only take a guess from here, but can you see if this works?
Map function is used to transform objects in an array and you need to return a value in the map function for the transform to happen. The transformed values are stored in a new array called newData. That is returned outside.
case ITEM_OVERLAY_TOGGLE:
var newData = state.data.map((item) => {
if (item.id === action.id) {
return {
...item,
overlayVis:false
}
} else {
return {
...item,
overlayVis : true
}
}
});
return {
...state,
data: newData
}
Edit: If action.id is also the index of the array, try this
case ITEM_OVERLAY_TOGGLE:
// object to be updated and returned
return {
...state,
data : [
...state.data.slice(0,action.id).map(i=>{ i.overlayVis = true, return i; }),
state.data[action.id].overlayVis=false,
...state.data.slice(action.id+1).map(i=>{ i.overlayVis = true, return i; })
]
};