I have a problem with adding obj to my arr in redux store.
I want to check if some element in my arr have the same id with payload id - I don't want to push, if not push the object to array.
The initial state of the array - [] (empty)
MY REDUCER CODE:
case "UNSHIFT_COUNTRY": {
console.log("P", payload);
return {
...state,
selectedCountries: [
...state.selectedCountries,
state.selectedCountries.forEach(item => {
if (item.id !== payload.id) {
state.selectedCountries.unshift(payload);
}
})
]
};
}
PAYLOAD IS AN OBJECT
Thanks for answers!
Use find to check if your array has an object with the same id:
case "UNSHIFT_COUNTRY": {
const found = state.selectedCountries.find(country => country.id === payload.id);
return found ? state : {
...state,
selectedCountries: [...state.selectedCountries, payload]
};
}
Related
I am new to react. I have set up a reducer with the state as an empty object. But when using the .map() function it doesn't seem to work on the object? Does the .map only work on arrays?
export const orders = (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case "NEW_ORDER":
const { new_order } = payload;
const new_state = { ...state, new_order };
console.log(new_state);
return new_state;
}
return state;
}
Well, no, you can not use .map() on an object since it is supposed to be used in an array. By the way, if you are trying to store a list of orders, you should use am array instead, so initialize your state with [] and not with {}, or with a key that actually contains your orders like { orders: [] } and then add the order you received like const new_state = { ...state, orders: [...state.orders, new_order] }.
You are correct. The map function is part of the Array prototype.
See here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
However, I don't see a map function in the code sample you posted?
If you want to loop over an object though you can use Object.entries.
const myObj = {foo: "bar"}
const result = Object.entries(myObj).map(([key, value]) => {
console.log(key) // "foo"
console.log(value) // "bar"
return `${key}-${value}`
})
console.log(result) // ["foo-bar"]
I'm working on my first solo ReactJS/Redux project and things were going well until I got to a point where I'm using an object in the Redux store that is always supposed to be a single object. When I copy the object from one part of the store (one element of the sources key) to another (the selectedItems key) that object is being stored as an array of length 1, which isn't the data I'm passing in (it's just a single object). I could live with this and just read out of that store variable as an array and just use element 0 except that when I call another method in the reducer to replace that variable in the store, that method stores the new data as a single object! My preference would be to have everything store a single object but I can't figure out how to do that. Anyway, here's some of the reducer code:
const initialState = {
sources: [
{
id: 1,
mfg: 'GE',
system: 'Smart bed',
model: '01',
name: 'GE smart bed'
},
{
id: 2,
mfg: 'IBM',
system: 'Patient monitor',
model: '03',
name: 'IBM patient monitor'
}
],
error: null,
loading: false,
purchased: false,
selectedItem: {}
};
// This is called when a user selects one of sources in the UI
// the Id of the selected sources object is passed in as action.id
// This method creates an array in state.selectedItem
const alertSourceSelect = ( state, action ) => {
let selectedItem = state.sources.filter(function (item) {
return item.id === action.id;
});
if (!selectedItem) selectedItem = {};
return {...state, selectedItem: selectedItem};
};
// When someone edits the selected source, this takes the field name and new value to
// replace in the selected source object and does so. Those values are stored in
// action.field and action.value . However, when the selected source object is updated
// it is updated as a single object and not as an array.
const selectedSourceEdit = ( state, action ) => {
return {
...state,
selectedItem: updateObject(state.selectedItem[0], { [action.field] : action.value })
};
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ALERT_SOURCE_SELECT: return alertSourceSelect( state, action );
case actionTypes.ALERT_SELECTED_SOURCE_EDIT: return selectedSourceEdit( state, action );
default: return state;
}
Here is the updateObject method (sorry I left it out):
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
};
Issue : updateObject is returning object and not array,and you are maintaining selectedItem as an array not object
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
};
Solution :
Either return array from updateObject :
export const updateObject = (oldObject, updatedProperties) => {
return [{
...oldObject,
...updatedProperties
}]
};
OR make array of returned object
const selectedSourceEdit = ( state, action ) => {
return {
...state,
selectedItem: [updateObject(state.selectedItem[0], { [action.field] : action.value })]
};
};
I started a little bit playing with redux and i am amazed so far.
My problem right now is, that my new reducer function changes the type of one state variable and i dont want that.
The state shall have a form like that:
I only want to delete an object from a jsons array:
pseudo:
delete state.items[item_index].jsons[json_to_delete_index]
I ended up with this reducer, which is returning the item state now as an object and not as an array.
case DELETE_JSON:
const item_index = state.items.findIndex((url) => url.url_id === action.payload.url_id);
const json_index = state.items[item_index].jsons.findIndex((json) => json.json_id === action.payload.json_id);
return {
...state,
items: {
...state.items,
[item_index]: {
...state.items[item_index],
jsons:
[
...state.items[item_index].jsons.splice(0, json_index),
...state.items[item_index].jsons.splice(json_index + 1)
]
}
}
};
I tried various approaches so far, but changing states inside highly nested objects seems still like a torture with redux. Does anybody maybe know a way to write it?
Changing state with highly nested objects can be difficult but map and filter functions are really helpful in this case
const item_index = state.items.findIndex((url) => url.url_id === action.payload.url_id);
const json_index = state.items[item_index].jsons.findIndex((json) => json.json_id === action.payload.json_id);
return {
...state,
items: state.items.map((item, index) => (index === item_index ?
{ ...item, item.jsons.filter((json, i) => (i !== json_index)) } : item))
};
I solved it by using update() from immutability-helpers.
Very handy
import update from 'immutability-helper';
/* some other code */
case DELETE_JSON:
const item_index = state.items.findIndex((url) => url.url_id === action.payload.url_id);
const json_index = state.items[item_index].jsons.findIndex((json) => json.json_id === action.payload.json_id);
return update(state, {
items: {
[item_index]: {
jsons: {$splice: [[json_index]]}
}
}
});
I have a cart data in this form
const cart = {
'1': {
id: '1',
image: '/rice.jpg',
price: 32,
product: 'Yellow Corn',
quantity: 2,
},
'2': {
id: '2',
image: '/rice.jpg',
price: 400,
product: 'Beans',
quantity: 5,
},
'3': {
id: '3',
image: '/rice.jpg',
price: 32,
product: 'Banana',
quantity: 1,
},
};
In the reducer file I have a function removeItem that is being consumed by the reducer
const removeItem = (items, id) => {
items[id] && delete items[id];
return items;
};
case REMOVE_ITEM: {
const { cart } = state;
const {
payload: { id },
} = action;
return {
...state,
cart: removeItem(cart, id),
};
}
In the component I am using this handleRemove() to handle the deletion
handleRemove = id => {
const {
actions: { removeItem },
} = this.props;
const payload = { id };
removeItem(payload);
};
Now in the redux developer tool, the change is working effectively but the component view is not updating.
Change removeItem function to below code
const removeItem = (items, id) => {
items[id] && delete items[id];
return {...items};
};
This is because component gets change only if reference changes. You can refer this link for more explanation
You need to create a copy of the cart, as otherwise React won't detect the change, because it does reference comparison and you return the same object.
Try to do the removeItem() in this way.
const removeItem = (items, id) => {
let itemsClone = [...items]; // Copies all items into a brand new array
itemsClone [id] && delete itemsClone [id]; // You perform the delete on the clone
return itemsClone ; // you return the clone
};
Do not mutate redux state, redux does not perform a deep diff check in your objects, when you do not mutate and create new objects, it is automatically detected as a different object, because its plain old js objects.
this would be good for further reading : immutable-update-patterns
so your removeItem method should be,
const removeItem = (items, id) => {
let {[id]: remove, ...rest} = items
return rest;
}
You can also use a library to do this, such as dot-prop-immutable , which has set, remove, merge methods to do relevant operations without mutating the object.
I am trying to update the property of an object which is stored in an array.
my state looks something like this:
state = {
todos: [
{
id: '1',
title: 'first item,
completed: false
},
{
id: '2',
title: 'second item,
completed: false
}
],
}
What I am trying to do is access the second element in the 'todos' array and update the completed property to either false -> true or true -> false.
I have a button with the handler for update, and my class method for the update looks like this:
onUpdate = (id) => {
const { todos } = this.state;
let i = todos.findIndex(todo => todo.id === id);
let status = todos[i].completed
let updatedTodo = {
...todos[i],
completed: !status
}
this.setState({
todos: [
...todos.slice(0, i),
updatedTodo,
...todos.slice(i + 1)
]
});
}
While this does work, I want to find out if there is a more concise way of achieving the same result; I tried to use Object.assign(), but that didn't work out because my 'todos' is an array, not an object. Please enlighten me with better code!
It would be best to use update function to make sure you don't work on outdated data:
onUpdate = (id) => {
this.setState(prevState => {
const copy = [...prevState.todos];
const index = copy.findIndex(t => t.id === id);
copy[index].completed = !copy[index].completed;
return { todos: copy }
})
}
You can simply copy your todos from state, then make edits, and after that put it back to the state
onUpdate = (id) => {
var todos = [...this.state.todos]
var target = todos.find(todo => todo.id == id)
if (target) {
target.completed = !target.completed
this.setState({ todos })
}
}