How do I update an array within an object from within state? - javascript

needing some help... how do I update the state to reflect a new schedule being added to a specific child (by id)?
I currently have a form that provides a new set of data that looks like this (with values from the form in the empty strings):
{
date: '',
parent: '',
activity: ''
}
I've created this function below, and I'm passing it the id of the child, and the new schedule which looks like the one above... I'm stuck on this one:
addSched = (id, schedule) => {
const newSched = this.state.children.map(child => {
if (child.id !== id) return child;
return {
...child,
schedules: schedule
};
});
this.setState({ children: newSched });
};
My current state looks like this:
state = {
children: [
{
id: 1,
firstName: 'Bella',
lastName: 'Laupama',
schedules: [
{
id: 1,
date: '25 December 2018',
parent: 'Chris',
activity: 'Christmas'
},
{
id: 2,
date: '31 December 2018',
parent: 'Laura',
activity: 'New Years Eve'
}
]
},
{
id: 2,
firstName: 'Cara',
lastName: 'Malane',
schedules: [
{
id: 1,
date: '25 December 2018',
parent: 'Chris',
activity: 'Christmas'
} ...etc
And the component that has the form has the following:
export default class AddSched extends React.Component {
state = {
date: '',
parent: '',
activity: ''
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
})
}
submitHandler = e => {
e.preventDefault()
this.props.addSched(this.props.id, this.state)
console.log('SUBMITTED:', this.state)
this.setState({
date: '',
parent: '',
activity: ''
})
}

You can use the array spread operator to concatenate the existing array plus the new schedule:
schedules: [...child.schedules, schedule]
Here's the complete function with the change:
ddSched = (id, schedule) => {
const newSched = this.state.children.map(child => {
if (child.id !== id) return child;
return {
...child,
schedules: [...child.schedules, schedule]
};
});
this.setState({ children: newSched });
};

Related

Map an object of properties where some properties do not exist

I currently am utilising Array.map to create a new object containing some data:
const bookList = [{
name: "Foo",
id: "1234",
quantity: 5,
}];
function mapBooks(bookList) {
return {
eventName: "ping",
data: {
list: {
books:
bookList.map(
({name, id, quantity }) => ({ name, id, quantity})
)
}
}
};
}
mapBooks(bookList);
// Result:
{
eventName: "ping",
data: {
list: {
books: {
name: "Foo",
id: "1234",
quantity: 5,
}
}
},
}
This is fine in this example, but what happens when one of the items is not in the provided data?
const bookList = [{
name: "Foo",
id: "1234",
}];
mapBooks(bookList);
// Result:
{
eventName: "ping",
data: {
list: {
books: {
name: "Foo",
id: "1234",
quantity: undefined,
}
}
},
}
How can I adjust my map function to simply not return any undefined values? For example I would prefer a result like this:
mapBooks(bookList);
// Result:
{
eventName: "ping",
data: {
list: {
books: {
name: "Foo",
id: "1234",
// quantity is simply not included
}
}
},
}
I don't know if i understand the question correctly but you could something like this:
const bookList = {
name: "Foo",
id: "1234",
quantity: 5,
};
function mapBooks(bookList) {
return {
eventName: "ping",
data: {
list: {
books:
bookList.map(
(book) => ({ ...book})
)
}
}
};
}
With the power of destructuring you will only fulled the present option of the object

How to Group JavaScript Array of Object based on key

So I have a data like this
const carts = [
{
name: 'Voucher A',
participants: [
{
date: 112
},
{
date: 112
}
],
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
},
{
name: 'Voucher B',
participants: [
{
date: 111
},
{
date: 112
}
],
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
}
]
And I want to group it based on the date (if it has same date). So for data above, the expected result will be
expected = [
{
name: 'Voucher A',
date: 1,
count: 1,
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
},
{
name: 'Voucher A',
date: 2,
count: 1,
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
}
]
Because it has different date. But if it has same date, the expected result will be
expected = [
{
name: 'Voucher A',
date: 1,
count: 2,
supplierName: 'ABC',
ticketDescription: 'Description of',
...data
}
]
I was trying to use reduce to group it but it did not give the structure I want
carts.forEach(cart => {
cart.participants.reduce((acc, obj) => {
acc[obj.date] = [...acc[obj.date] || [], obj]
return acc
}, {})
})
To organize the data, I think you need two associations to group by: the name and the dates and their counts for that name:
const carts = [
{
name: 'Voucher A',
participants: [
{
date: 1
},
{
date: 2
}
]
}
];
const groupedByNames = {};
for (const { name, participants } of carts) {
if (!groupedByNames[name]) groupedByNames[name] = {};
for (const { date } of participants) {
groupedByNames[name][date] = (groupedByNames[name][date] || 0) + 1;
}
}
const output = Object.entries(groupedByNames).flatMap(
([name, dateCounts]) => Object.entries(dateCounts).map(
([date, count]) => ({ name, date: Number(date), count })
)
);
console.log(output);
If you want use, just plain for loops, you can try this solution. It looks simple and elegant 😜😜
const carts = [
{
name: 'Voucher A',
participants: [
{
date: 1
},
{
date: 1
},
{
date: 2
}
]
},
{
name: 'Voucher B',
participants: [
{
date: 1
},
{
date: 2
},
{
date: 2
}
]
}
]
const finalOutput = []
for (const cart of carts) {
for (const participant of cart.participants) {
const res = finalOutput.find(e => e.name === cart.name && e.date === participant.date)
if (res) {
res.count += 1
} else {
finalOutput.push({ name: cart.name, date: participant.date, count: 1 })
}
}
}
console.log(finalOutput)
Use forEach and destructuring
const process = ({ participants, name }) => {
const res = {};
participants.forEach(({ date }) => {
res[date] ??= { name, count: 0, date };
res[date].count += 1;
});
return Object.values(res);
};
const carts = [
{
name: "Voucher A",
participants: [
{
date: 1,
},
{
date: 2,
},
],
},
];
console.log(carts.flatMap(process));
const carts2 = [
{
name: "Voucher A",
participants: [
{
date: 1,
},
{
date: 1,
},
],
},
];
console.log(carts2.flatMap(process));

Is it possible to only update array state of objects?

I was wondering if there is any documentation or reference that could help me to to understand how to update a state of objects array (Without duplicates).
My state looks like this:
accounts: [
{ name: ‘mike’ },
{ name: ‘tee’ },
{ name: ‘ralf’ },
{ name: ‘candy’ },
{ name: ‘bon’ },
{ name: ‘salm’ },
{ name: ‘shark’ },
{ name: ‘tof’ },
{ name: ‘hulk’ },
{ name: ‘zar’ },
{ name: ‘blake’ },
],
the upcoming array is like this:
accounts: [
{ name: 'mike’, balance: ’1000’},
{ name: 'tee’, balance: ’235345’},
{ name: 'zar’, balance: ’3455’},
{ name: 'candy’, balance: ’567567’},
{ name: 'tee’, balance: ’8767’},
{ name: 'salm', balance: ’234’},
{ name: 'blake', balance: ’134’},
],
So the updated state on setState will look like this:
accounts: [
{ name: 'mike’, balance: ’1000’},
{ name: 'tee’, balance: ’235345’},
{ name: ‘ralf’ },
{ name: 'candy’, balance: ’567567’},
{ name: ‘bon’ },
{ name: 'salm', balance: ’234’},
{ name: ‘shark’ },
{ name: ‘tof’ },
{ name: ‘hulk’ },
{ name: 'zar’, balance: ’3455’},
{ name: 'blake', balance: ’134’},
],
I have tried with prevState.accounts.concat(accounts) but it only adds duplicates.
Find object based on condition and then update values where use Array#find method to find the element and Object.assign method to copy values to an existing object.
accounts.forEach( o => {
let oldObj = prevState.accounts.find(o1 => o1.name === o.name);
Object.assign(oldObj, o)
})
Final code would be like :
this.setState(prevState => {
newAccounts.forEach(o => {
let oldObj = prevState.accounts.find(o1 => o1.name === o.name);
Object.assign(oldObj, o)
})
return prevState.accounts
});
Oneliner solution by creating a new array.
this.setState(prevState => newAccounts.map(o => Object.assign(prevState.accounts.find(o1 => o1.name === o.name), o)));
// if you don't want to mutate original object in previous state then
this.setState(prevState => newAccounts.map(o => Object.assign({}, prevState.accounts.find(o1 => o1.name === o.name), o)));
// or with ES6 spread syntax
this.setState(prevState => newAccounts.map(o => ({ ...prevState.accounts.find(o1 => o1.name === o.name), ...o }))));
If your new state accounts is always going to be subset of the previous state accounts value. You can use something like this
this.state = {
accounts : [
{ name: 'mike' },
{ name: 'tee' },
{ name: 'ralf' },
{ name: 'candy' },
{ name: 'bon' },
{ name: 'salm' },
{ name: 'shark' },
{ name: 'tof' },
{ name: 'hulk' },
{ name: 'zar' },
{ name: 'blake' },
]
}
const newAccounts = [
{ name: 'mike', balance: 1000},
{ name: 'tee', balance: 235345},
{ name: 'zar', balance: 3455},
{ name: 'candy', balance: 567567},
{ name: 'tee', balance: 8767},
{ name: 'salm', balance: 234},
{ name: 'blake', balance: 134},
]
this.setState({accounts: this.state.accounts.map (x => ({...x, ...newAccounts.find(y => y.name === x.name)}))});
You can use Array.find to find the values of old state accounts in new accounts and then use ... or Object.assign to merge properties.

React-Redux: deleting an item does not re-render the array

I'm deleting invitations by passing their IDs to the back end, which works. However, my reducer is not working properly to re-render the new, filtered array of invitations. When I refresh the page, the deleted invitation is gone. What am I doing wrong?
The action:
export function deleteInvitation(id) {
const user = JSON.parse(localStorage.getItem('user'));
console.log('now deleting id ', id);
return function(dispatch) {
axios
.delete(`${ROOT_URL}/invitation/`, {
headers: { authorization: user.token },
params: { id: id }
})
.then(response => {
console.log(id);
dispatch({
type: DELETE_INVITATION,
id
});
});
};
}
The reducer:
export default function(state = {}, action) {
switch (action.type) {
case INVITATION_SUCCESS:
return { ...state, invited: true, error: {} };
case INVITATION_FAILURE:
return { ...state, invited: false, error: { invited: action.payload } };
case FETCH_INVITATIONS:
return { ...state, invitations: action.payload };
case DELETE_INVITATION:
return {
...state,
invitations: state.invitations.filter(_id => _id !== action.id)
};
default:
return state;
}
}
I'm making a guess about the structure of the invitations array...
In the reducer, the filter function appears to be incorrect. The action is passing an id property, which I'm guessing is a property of an invitation object. But the filter function is filtering objects from state.invitations where the object is the id. That is, the reducer is doing something like this:
const action = {id: 0}
const invitation = [{
_id: 0,
name: 'Name 0',
location: 'Location 0'
},
{
_id: 1,
name: 'Name 1',
location: 'Location 1'
},
{
_id: 2,
name: 'Name 2',
location: 'Location 2'
}
];
console.log(invitation.filter(_id => _id !== action.id));
which will return the full original array because the filter function is checking for the inequality of action.id (a number) to an invitation (an object). Basically:
{
_id: 0,
name: 'Name 0', !=== number
location: 'Location 0'
}
will return true for any num and/or any invitation object, so the filter function will return every item in state.invitations.
To correct this, check the invitation._id against the action.id, like this:
const action = {id: 0}
const invitation = [{
_id: 0,
name: 'Name 0',
location: 'Location 0'
},
{
_id: 1,
name: 'Name 1',
location: 'Location 1'
},
{
_id: 2,
name: 'Name 2',
location: 'Location 2'
}
];
console.log(invitation.filter(invitation => invitation._id !== action.id));

react redux array nested Tree Menu

I'm a learner developer, and I'm build a app with a tree menu(react + redux + sagas), but I'm getting some errors of Mutation State, I saw what best practices is stay de state flat as possible, but I didn't finded one menu tree what work with a flat state, so my data is look this:
menuTree: [{
id: 'id-root',
name: 'root',
toggled: true,
children: [
{
id: 'id-parent1',
name: 'parent1',
toggled: true,
children: [
{
id: '123',
name: 'parent1_child1'
},
{
id: '234',
name: 'parent1_child2'
}
]
},
{
id: 'id-loading-parent',
name: 'loading parent',
loading: true,
children: []
},
{
id: 'id-parent2',
name: 'parent2',
toggled: true,
children: [
{
id: 'parent2_children1',
name: 'nested parent2',
children: [
{
id: '345',
name: 'parent2 child 1 nested child 1'
},
{
id: '456',
name: 'parent2 child 1 nested child 2'
}
]
}
]
}
]
}],
And my redux action:
case types.SOLUTION__MENUCURSOR__SET:
// console.log('action payload', action.payload);
// console.log('state', state);
const cursor = action.payload.cursor;
// console.log('set menu cursor action', cursor);
return {
...state,
menuTree: state.menuTree.map(
function buscaIdMenuTree(currentValue, index, arr){
if(currentValue.id){
if(currentValue.id.includes(cursor.id)){
currentValue.toggled = action.payload.toggled;
return arr;
}else{
if(currentValue.children)
{
currentValue.children.forEach(function(currentValue, index, arr){
return buscaIdMenuTree(currentValue, index, arr);
});
}
}
return arr;
}
}
)[0]
};
The code works but I get Mutation State Error, so someone can help me to fix it ?
You can rebuild your menu as a plain list:
let menuTree = [{
id: 'id-root',
name: 'root',
toggled: true,
parent: null
},{
id: 'id-parent1',
name: 'parent1',
toggled: true,
parent: 'id-root'
},{
id: '123',
name: 'parent1_child1',
parent: 'id-parent1'
},{
id: '234',
name: 'parent1_child1',
parent: 'id-parent1'
},
{
id: 'id-loading-parent',
name: 'loading parent',
loading: true,
parent: 'id-root'
},{
id: 'id-parent2',
name: 'parent2',
toggled: true,
parent: 'id-root'
},{
id: 'parent2_children1',
name: 'nested parent2',
parent: 'id-parent2'
},{
id: '345',
name: 'parent2 child 1 nested child 1',
parent: 'parent2_children1'
},
{
id: '456',
name: 'parent2 child 1 nested child 2',
parent: 'parent2_children1'
}]
then if your menu renderer require a tree you can convert the list to a tree so inside the component renderer this.menuTree will be a tree:
const buildTree = (tree, cParent = null) => {
return tree.filter(cNode => cNode.parent == cParent).reduce((curr, next) => {
let cNode = {...next, children: buildTree(tree, next.id)}
delete cNode.parent
return [...curr, cNode]
}, [])
}
function mapStateToProps(state) {
return {
mapTree: builTree(state.mapTree)
};
}
export default connect(mapStateToProps)(YourComponent);
Inside the mutation now you just need to create a list of node that needs to be toggled and then map the state accordingly
case types.SOLUTION__MENUCURSOR__SET:
// console.log('action payload', action.payload);
// console.log('state', state);
const cursor = action.payload.cursor;
// console.log('set menu cursor action', cursor);
const getToggleList = (tree, cursor) => {
let target = tree.find(cNode => cNode.id == cursor.id)
if(target.parent != null){
let parent = tree.find(cNode => cNode.id == target.parent)
return [target.parent, ...getToggleList(tree, parent)]
}else{
return []
}
}
let toggleList = [cursor.id, ...getToggleList(state.menuTree, cursor.id)]
return {
...state,
menuTree: state.menuTree.map(node => ({...node, toggle: toggleList.includes(node.id)}))
};

Categories

Resources