How can I replace the value in an array using redux? - javascript

I'm trying to overwrite a specific value in my Redux state which is an array. I have gotten the index already and also the value of the new text. I'm just not sure about the best way of overwriting the previous text. Here is my reducer so far. The UPDATE_LINK is the one I'm having trouble with.
export function linkList(state = [], action) {
switch(action.type) {
case 'ADD_LINK':
var text = action.text;
console.log('Adding link');
console.log(text);
return {
...state,
links: [text, ...state.links]
};
case 'DELETE_LINK':
var index = action.index;
console.log('Deleting link');
return {
...state,
links: [
...state.links.slice(0, index),
...state.links.slice(index + 1)
],
};
case 'UPDATE_LINK':
var index = action.index;
var newText = action.newText;
console.log(action.newText);
console.log(action.index);
return {
...state,
// How do I update text?
}
default:
return state;
}
};
export default linkList;

You could use Array.protoype.map to return the existing entries where available and a new entry where the index matches:
var index = action.index;
var newText = action.newText;
return {
...state,
links: state.links.map((existingLink, currentIndex) => index === currentIndex ? newText : existingLink)
}
Or, following your existing DELETE_LINK logic:
return {
...state,
links: [
...state.links.slice(0, index),
newText,
...state.links.slice(index + 1)
],
};

Related

Redux deep clone - state is always equal

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,

Reducer updated with wrong value(array got updated with one item with few items inside instead spreading them)

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.

How to replace an object property value in redux

I am trying to create an online shop using redux. I have got it so that a person can add an item to their basket. However, having difficulty with adding quantity. I have a method that works that wont let someone add the same product twice, I just need to now make it increase the quantity for that same product.
My basket state is stored as an array of objects.
This is my basket reducer:
const initialState = [];
const isProductInBasket = (state, action) => {
for (var i=0; i < state.length; i++){
if(state[i].product.id == action.data.product.id){
return true;
}
}
}
export default (state = initialState, action) => {
switch(action.type) {
case "ADD_TO_BASKET":
if (isProductInBasket(state, action)) {
for (var i=0; i < state.length; i++){
if(state[i].product.id = action.data.product.id){
console.log(state, 'stst');
const basketState = state[i].product.quantity + 1;
return {...state, basketState}; //problem is here
}
}
}
else {
const basketState = [].concat(state).concat(action.data)
return basketState;
break;
}
default:
return state
};
};
Clearly what im doing is wrong as im returning an object, but im wondering how i can return that new object in place of the old object. i need to return it as an object but inside an array...
just to be uber clear, when I have this:
{name: "Rucksack", price: "15.00", id: 1, quantity: 0}
and I click add to basket, it should then come back as:
{name: "Rucksack", price: "15.00", id: 1, quantity: 1}
I'd recommend reading this section of the Redux docs - it shows you how to update an individual element in an array without mutation.
Effectively, what you need to do is create a new array that has a modified copy of your basket item. When you need to perform a transformation on an array without mutating, Array.prototype.map is your friend:
if (isProductInBasket(state, action)) {
return state.map(product => {
if (product.id == action.data.product.id) {
return { ...product, quantity: product.quantity + 1 };
}
return product;
});
}
You could use findIndex to check if the object already exists and update it else push the payload data into state
switch(action.type) {
case "ADD_TO_BASKET":
const index = state.findIndex(productData => productData.product.id === action.data.product.id);
if(index > -1) {
return [
...state.slice(0, index),
{
...state[index],
product: {
...state.product,
quantity: state[index].product.quantity + 1
}
},
...state.slice(index + 1)
]
}
return [...state, action.data]

state.findIndex is not a function error with findIndex

I'm passing the id of the object as the action.payload to the reducer to modify the object.
Reducer:
const initialState = {
posts: [
{id:1, name:'post1', number:11},
{id:2, name:'post2', number:22},
{id:3, name:'post3', number:33}
]
}
export default function newData (state = initialState, action) {
switch (action.type) {
case "updateNumber": {
// find object to update
const index = state.findIndex(({ id }) => id == action.payload);
if (index > -1 ) {
const toIncrement = state[index];
const number = toIncrement.number++;
// create new object with existing data and newly incremented number
const updatedData = { ...toIncrement, number };
// return new array that replaces old object with incremented object at index
return [...state.slice(0, index), updatedData, ...state.slice(index + 1)];
}
// return state if no object is found
return state;
}
default:
return state
}
}
But I'm getting error: state.findIndex is not a function. How to find the index of the element in the posts array? console.log actions is giving me {type: "updateNumber", payload: 2} where payload is the element pressed.
UPDATE1:
export default function newData (state = initialState, action) {
switch (action.type) {
case "updateNumber": {
// find object to update
const index = state.posts.findIndex(({ id }) => id == action.payload);
if (index > -1 ) {
const toIncrement = state.posts[index];
const number = toIncrement.posts.number++;
// create new object with existing data and newly incremented number
const updatedData = { ...toIncrement, number };
// return new array that replaces old object with incremented object at index
return [...state.posts.slice(0, index), updatedData, ...state.posts.slice(index + 1)];
}
// return state if no object is found
return state;
}
default:
return state
}
}
So this is supposed to return the posts with updated number in the state, right?
Your initialState is an object.
I think you meant
state.posts.findIndex(({ id }) => id == action.payload);
Or maybe change the initialState to
const initialState = [
{id:1, name:'post1', number:11},
{id:2, name:'post2', number:22},
{id:3, name:'post3', number:33}
]
Edit
As a followup to your edit,
After your change, Now you can do:
const number = toIncrement.number++;
As totalIncrement will hold an object like this for example:
{id:1, name:'post1', number:11}
Edit #2
I think you are mutating the state which is not allowed in redux.
Try changing this:
if (index > -1 ) {
const toIncrement = state.posts[index];
const number = toIncrement.posts.number++;
To this:
if (index > -1 ) {
const toIncrement = {...state.posts[index]};
const number = toIncrement.posts.number + 1; // i hope this is a number and not a string!
Another thing, Your initial state is an object but your reducer returns an array.
Change this line:
// return new array that replaces old object with incremented object at index
return [...state.posts.slice(0, index), updatedData, ...state.posts.slice(index + 1)];
To this line:
// return new array that replaces old object with incremented object at index
return { posts: [...state.posts.slice(0, index), updatedData, ...state.posts.slice(index + 1)]};

Multiple updates to an array in a redux reducer

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; })
]
};

Categories