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