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; })
]
};
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'm looking for a way to better organize my actions/reducers, now it looks like this:
[SOME_ACTION]: state => ({
...state,
isModified: true,
orders: {
...state.orders
// or maybe change something here
},
documentation: {
... state.documentation
// or maybe change something here
}
})
The problem is that the object has deep nesting, and I have to keep track of every level of it and add it everywhere accordingly. And I have to write all of that just to update a single value.
What are my options? For now I just want to be able to update one/several values on some level of nesting by just point on what I want to change.
You only have to copy the object that changes and its parent objects, not its child objects. For instance, in your example I assume the change is isModified: true. You don't need to copy orders or documentation, you can reuse them because they haven't changed:
[SOME_ACTION]: state => ({
...state,
isModified: true
})
You can give yourself a utility function to do changes if you like. For instance:
function newState(state, path, value) {
const result = {...state};
const lastKey = path[path.length - 1];
let obj = state;
for (const key of path.slice(0, path.length - 1)) {
obj = obj[key];
}
obj[lastKey] = value;
return result;
}
...or similar (that's off the cuff).
In that specific case it's not all that useful since the change is shallow:
[SOME_ACTION]: state => newState(state, ["isModified"], true)
But if you had a change to make deeper in the structure:
[SOME_ACTION]: state => newState(state, ["orders", someOrderId, "fulfilled"], true)
...it's a bit less of a pain to write than the equivalent:
[SOME_ACTION]: state => ({
..state,
orders: {
...state.orders,
[someOrderId]: {
...state.orders[someOrderId],
fulfilled: true
}
}
})
Example:
const state = {
isModified: false,
orders: {
"order1": {
customer: "Joe Bloggs",
fulfilled: false
},
"order2": {
customer: "Jane Doe",
fulfilled: false
}
},
documentation: {
foo: {},
bar: {}
}
};
const someOrderId = Math.random() < 0.5 ? "order1" : "order2";
const updated = newState(state, ["orders", someOrderId, "fulfilled"], true);
console.log(`Updated order '${someOrderId}':`);
console.log(updated);
function newState(state, path, value) {
const result = {...state};
const lastKey = path[path.length - 1];
let obj = state;
for (const key of path.slice(0, path.length - 1)) {
obj = obj[key];
}
obj[lastKey] = value;
return result;
}
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
};
i'm having hard time figure out this. Have component which is search filter and pushes all selected filters into url. Everything works like it should except in case of refresh, in that case reducer is updated for selected filter with array with single item in which i have all selected items, not spreaded into array.
f.e. i have url
myexampleapp.com/alltrips?tripType=short_walk,cycling,downhill_cycling,long_walks&season=spring,summer,alle,vinter&lengthTo=50
my reducer
// ------------------------------------
// Constants
// ------------------------------------
export const UPDATE_FILTERS = 'UPDATE_FILTERS';
// ------------------------------------
// Actions
// ------------------------------------
const updateFilter = (key, value) => ({
type: UPDATE_FILTERS,
payload: {
key,
value
}
});
// ------------------------------------
// Action creators
// ------------------------------------
export const updateFilterState = (key, value) => {
return dispatch => {
dispatch(updateFilter(key, value));
};
};
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
tripType: [],
season: [],
tripsTo: undefined,
tripsFrom: undefined
};
export function filterReducer (state = initialState, action) {
switch (action.type) {
case UPDATE_FILTERS: {
const key = action.payload.key;
const value = action.payload.value;
if (key === 'tripsFrom' || key === 'tripsTo') {
return Object.assign({}, state, { [key]: value });
} else {
var newFilter = state[key].slice();
var ttIdx = state[key].indexOf(value);
if (ttIdx !== -1) {
newFilter.splice(ttIdx, 1);
} else {
newFilter.push(value);
}
}
console.log(newFilter);
return Object.assign({}, state, { [key]: newFilter });
}
default:
return state;
}
}
console.log returns array with 1 element in which have array with 5 elements. but i want that 5 ekements to be in parrent array.
and i'm parsing URL
componentDidMount () {
let {
location: { search },
updateFilterState
} = this.props;
search = search.slice(1);
var queries = search.split('&');
queries.forEach(q => {
var tmp = q.split('=');
if (tmp[0] && tmp[1]) {
if (tmp[0].toLowerCase() === 'triptype') {
updateFilterState(tmp[0], tmp[1].split(','));
console.log(tmp[1].split(','));
} else if (tmp[0].toLowerCase() === 'tripsto') {
updateFilterState(tmp[0], tmp[1]);
} else if (tmp[0].toLowerCase() === 'tripsfrom') {
updateFilterState(tmp[0], tmp[1]);
} else if (tmp[0].toLowerCase() === 'season') {
updateFilterState(tmp[0], tmp[1].split(','));
}
}
});
this.updateQuery(this.props);
}
So everything works except when i want to refresh.
Pretty new with all this, and been stuck for almost 3 days with this. Hope you understand what im trying to ask here as i'm pretty new and non-english speaker, so i don't know all the terms so i can better express myself. Can someone give me some pointers?
If I'm not mistaken you are feeding the reducer with an array for season and tripType. So, when you try to update those values, you are not actually spreading that array. This is your value parameter. Hence, if you do this you will have a parent array with your desired result:
newFilter.push(...value);
... is ES6's spread syntax. So we are spreading our array and pushing it into our newFilter.
But again if I don't see it wrong you will have problems with this code since you are not checking the existence of your values right. You are looking indexOf something but if you really feeding your reducer with an array, for which one you are looking this index?
Here is a cleaner way of doing this if I'm not mistaken what you are trying to do here:
export function filterReducer (state = initialState, action) {
switch (action.type) {
case UPDATE_FILTERS: {
const { key, value } = action.payload;
if (key === 'tripsFrom' || key === 'tripsTo') {
return { ...state, [key]: value };
}
const newFilter = Array.isArray(value)
? [ ...new Set( [ ...state[key], ...value ] ) ]
: [ ...new Set( [ ...state[key], value ] ) ];
return { ...state, [key]: newFilter};
}
default:
return state;
}
}
Some differences with your code:
I am using spread syntax instead of Object.assign.
Instead of checking all the existence values (iterating the array and doing some logic) I'm using here Set object. It creates an object of unique values of what we give it. So I am cheating here and spreading our old state with spreading our value into an array, give this to our Set, and again at the top level spreading it again into an array. If you don't do the last spread you will get an object but here we want an array.
I'm looking for a pure function, to modify my immutable state object. The original state given as parameter must stay untouched. This is especially useful when working with frameworks like Redux and makes working with immutable object in javascript much easier. Especially since working with the object spread operator using Babel is already possible.
I did not found anything better than first copy the object, and than assign/delete the property I want like this:
function updateState(state, item) {
newState = {...state};
newState[item.id] = item;
return newState;
}
function deleteProperty(state, id) {
var newState = {...state};
delete newState[id];
return newState;
}
I feel like it could be shorter
Actions on state, where state is considered immutable.
Adding or Updating the value of a property:
// ES6:
function updateState(state, item) {
return Object.assign({}, state, {[item.id]: item});
}
// With Object Spread:
function updateState(state, item) {
return {
...state,
[item.id]: item
};
}
Deleting a property
// ES6:
function deleteProperty(state, id) {
var newState = Object.assign({}, state);
delete newState[id];
return newState;
}
// With Object Spread:
function deleteProperty(state, id) {
let {[id]: deleted, ...newState} = state;
return newState;
}
// Or even shorter as helper function:
function deleteProperty({[id]: deleted, ...newState}, id) {
return newState;
}
// Or inline:
function deleteProperty(state, id) {
return (({[id]: deleted, ...newState}) => newState)(state);
}
An ES6 solution, that has a bit more support is Object.assign:
const updateState = (state, item) => Object.assign({}, state, { [item.id]: item });
In a Map Function
To do this process within a map function (remove an attribute and add a new attribute on each object), given an array of objects -
const myArrayOfObjects = [
{id: 1, keyToDelete: 'nonsense'},
{id: 2, keyToDelete: 'rubbish'}
];
Delete the attribute keyToDelete, and add a new key newKey with the value "someVar".
myArrayOfObjects.map(({ keyToDelete, ...item}) => { ...item, newKey:'someVar'});
Updating the array to
[
{id: 1, newKey:'someVar'},
{id: 2, newKey:'someVar'}
]
See this great post for more information on the deletion method.
Instead of writing boilerplate code (as answered above: (({[id]: deleted, ...state}) => state)(state)) which is hard to read, you could use some library to do the same:
https://github.com/cah4a/immutable-modify
https://github.com/kolodny/immutability-helper
https://github.com/M6Web/immutable-set
https://github.com/bormind/immutable-setter
For example:
import {remove} from 'immutable-modify'
function updateState(state, item) {
return remove(state, item.id)
}
It's also supports any nested updates:
import {set} from 'immutable-modify'
function updateState(state, item) {
return set(state, 'user.products', (products) => ({
...products,
items: products.items.concat(item),
lastUpdate: Date.now()
}))
}
Try:
const { id, ...noId } = state;
And test:
console.log(noId);
Removing item from an array, just use filter ;)
CASE 'REMOVE_ITEM_SUCCESS':
let items = state.items.filter(element => element._id !== action.id);
return {
...state,
items
}