Why is my state update overwriting existing data values? - javascript

Below I am trying to take a copy of the current state of the users data, along with a copy of a new invoice template pulled from a json file that has the key value pairs all blank.
I then change the templates invoiceID to be 1 more than the idx (this is a number value sent from a child component that is the length of all the invoices in state).
Lastly, I take the copy of the users data and add on the new template, then save that new users data back into state so it can be updated within my list.
createInvoice = idx => {
let newUserData = this.state.userData;
let template = UsersJSON[0].invoices[0];
template.invoiceID = idx + 1;
newUserData.invoices.push(template);
this.setState({
userData: newUserData
});
}
This is the current state of all the data when I log in:
After I click New Invoice + once:
The problem starts happening after I click New Invoice + more than once:
ONLY all of the NEW Invoice ID's keep being updated to the latest and greatest IDs. I seriously have no clue why this is happening. Any help would be appreciated!
A link to my project on github (look on the invoices branch, not master):
https://github.com/Brent-W-Anderson/invoice-pdf/tree/invoices

Issues
You are not correctly creating a new array reference for state and react reconciliation.
You are also mutating your template reference object.
Code
createInvoice = idx => {
let newUserData = this.state.userData; // <-- saved state reference
let template = UsersJSON[0].invoices[0];
template.invoiceID = idx + 1; // <-- template mutation
newUserData.invoices.push(template); // <-- mutated state
this.setState({
userData: newUserData // <-- saved state reference back into state
});
}
Solution
Create a shallow copy of all state you intend to update.
createInvoice = idx => {
let newInvoices = [...this.state.userData.invoices]; // <-- create a new array reference
let template = {
...UsersJSON[0].invoices[0], // <-- create new template object reference
};
template.invoiceID = idx + 1;
newInvoices.push(template);
this.setState({
userData: {
...state.userData,
invoices: newInvoices,
}
});
}
A slightly more react-y way to add to state is to map the data from the previous state and spread in the template so you are also not mutating it.
createInvoice = idx => {
this.setState(prevState => ({
userData: {
...prevState.userData,
invoices: [
...prevState.userData.invoices,
{
...UsersJSON[0].invoices[0],
invoiceID: idx + 1,
},
],
},
}));
}

You want to copy a state object (and in JS arrays are objects) before updating it. Maybe let newUserData = [...this.state.userData] would be the way to avoid this bug, but you might need a 'deep copy.'
See https://dev.to/andyrewlee/cheat-sheet-for-updating-objects-and-arrays-in-react-state-48np for more on this topic.

Related

Properly append to an array when handling presses

Within my function, through interaction from the user, I aim slowly build up an array of responses which I then pass off to an API. However, different approaches to append to the array, simply return a single position array (overwrite).
My current code as follows:
const contribution: Array = [];
const handlePress = () => {
var col = {
response,
user: 1,
update: update.id,
question: q.id,
};
contribution = [...contribution, col];
}
My understanding is that contribution = [...contribution, col] is the correct way to add to the array.
What is the best practice approach for doing this inside a function called each time the user interacts?
Although it is not clear from the question, I suspect, this code is inside a component. If so, then a new contribution array is created on every render. You need to use useState to store this array so that a new array is not created on every render.
const [contribution, setContribution] = React.useState([]);
const handlePress = () => {
var col = {
response,
user: 1,
update: update.id,
question: q.id,
};
setContribution([...contribution, col]);
}

How do I change an object's value in an IndexedDB index?

Is it possible to update an object's value within an IndexedDB index without cloning, deleting, or putting a new entry? Theoretically something like the following snippet would do the trick, though it probably would not delete until the put was confirmed. But it looks like overkill to me. It looks like it would be a nightmare to do any error handling on.
const objectStore = db.transaction([objectStoreName], 'readwrite')
.objectStore(objectStoreName);
const requestGet = objectStore.get(index);
requestGet.onsuccess = (event: any) => {
const value = event.target.result.value // Store old value
const requestDelete = objectStore.delete(index);
requestDelete.onsuccess = (event: any) => {
const requestPut = objectStore
.put({index: 'New Index Value', value: value}); // Put back using new index
};
};
You cannot directly change values in an object store's index. You can change the values of an object in an object store, and IndexedDB will propagate your changes to related indices. Indices are essentially read-only.
It is possible since you specify your index, otherwise, an other logic may be necessary.
As you should know, the IDBObjectStore has a method .put() which it will receive two params. With it you can either PUT a new value or UPDATE a value.
IDBObjectStore.put(item, key)
item: The item you want to put/update
key: opcional: Your primary object store key (such as an uuid, a random number, in short...) for that item you would like to update.
Code:
//This is an example only.
//Let's think that we have an object store into our IndexDB 'user', where object store is called by user-data:
//# Key Value
//0 1 { username: 'John Doe' }
//Here, we are receiving the 'success' result from an indexedDB.open(), and using its result with a promise.
dbPromise.then(db => {
//Getting the transaction
const transaction = db.transaction('user-data', 'readwrite')
//Getting the objectStore with the data, the same object store before.
const store = transaction.objectStore('user-data')
//Getting the key's object store, in the other other words, this is the key you define when you create you objectStore, with createObjectStore. In this example, I've used 'autoIncrement: true'
const query = store.get(1)
//Getting the query result with a success listener.
query.addEventListener('success', event => {
const { ['result']: user } = event.target
user.productsIntoCart.push(newItem)
//With this, we will be able to change the object store value.
user.username = 'Jane Doe'
store.put(user, 1)
})
query.addEventListener('error', event => console.error(event))
transaction.addEventListener('complete', () => db.close())
})
//# Key Value
//0 1 { username: 'Jane Doe' }
You can see more details you want in the MDN IDBObjectStore.put documentation.
IDBObjectStore

How to create a `context.Provider`/`context.Consumer`-like structure to pass values in a bot app?

I'm trying to pass a property, that is inside the first position of an array of objects, to another module so I can use this value later. I've tried to pass it as module(args), but it keeps reading the default value which is 0. Is there a way to do this?
I tried to implement some React.context but the Bot framework Emulator is refusing it.
/////////////////Module that ll acquire the value/////////////////////////////
getCard(bot, builder, params) {
let configValues = { ...params[0] }
bot.dialog(`${configValues.path}`, function (session) {
var msg = new builder.Message(session);
const cardItem = (obj) => {
return (new builder.HeroCard(session)
.title(`${obj.title}`)
.text(`R$ ${obj.price}`)
.images([builder.CardImage.create(session, `${obj.img}`)])
.buttons([
builder.CardAction.imBack(session, `${obj.price} Item adicionado!`, 'add to cart')
// !onClick event must add the current obj.price to
// the configValues.total(Ex: configValues.total += obj.price)!
])
)
}
msg.attachmentLayout(builder.AttachmentLayout.carousel)
msg.attachments(
eval(params.map(obj => cardItem(obj)))
);
//!in here before end the dialog is where i want to update
// the configValues.total so i can show it in the -> Checkout module
session.send(msg).endDialog()
}).triggerAction({ matches: configValues.regex });
}
}
//////////////CheckOut.Module///////////////////////////////
{...}
let configValues = { ...params[0] }
let state = {
nome: "",
endereco: "",
pagamento: "",
total: configValues.total // this is the value to be read
}
bot.dialog('/intent', [
{...},
(session, results) => {
state.pagamento = results.response
session.send(
JSON.stringify(state) // here is the place to be printed
)
{...}
]
).triggerAction({ matches: /^(finalizar|checar|encerrar|confirmar pedido|terminar)/i })
Since you solved your original problem, I'll answer the one in your comment.
Your problem is here:
cartId.map((obj, i , arr) => {
// if (!obj.total) {
// obj.total.reduce(i => i += i)
// }
const newtotal = new total
newtotal.getTotals(bot, builder, obj, arr)
})
cartId contains the totals for each of your items. When you call map on it, you're passing each item individually to getTotals, which passes each item to checkout()
The reason you can't sum all of the totals and can only sum one item's total is that you pass cartId to checkout and cartId has been changed to just a single item. Instead, there's a couple of different things you could do:
Pass the whole cartId from cartItems and use something like for (var key in cartItems) in totalConstructor() and checkoutConstructor(). This is probably the easiest, but not very memory efficient.
Use BotBuilder's State Storage to store your totals array in userData, then sum that at the end. This might be more difficult to implement, but would be a much better route to go. Here's a sample that can help you get started.

Pass value from map to state

//It's working now - updated code
I'm working on my own autocomplete component because I have problem with passing firebase data to a ready one.
The whole mechanism is working good but I have problem with passing values after getting user input
I'm setting initial state with those values
const INITIAL_STATE = {
allChars: [],
suggestions: [],
value: ""
};
Then in autocomplete class i'm loading all users from database
loadData(){
let self = this;
let characters = firebase.firestore().collection("users");
characters.get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
let document = doc.data();
self.setState(({allChars})=>({
allChars: [
...allChars,
document
]
}))
});
});
}
Here is my getSuggestions function. It is firing on input change
getSuggestions = event => {
const {value, suggestions} = event.target;
this.setState({
value: value,
suggestions: []
})
let suggest = [];
this.state.allChars.map((allChars) => {
if(value.length > 1 && allChars.name.toLowerCase().includes(value.toLowerCase())){
suggest.push (
allChars.name
);
}
})
this.setState({
suggestions: suggest
})
}
In render I just put {sugestions}
But in {suggestions} I get rendered only one name.
one
But when I console.log it - I get two names
two
There should be two.
I tried to set state in this function like in loadData(), but I still get only one value.
Is there other way to get both values into DOM
Full code can be found here: https://github.com/Ilierette/react-planner/blob/master/src/component/elements/Autocomplete.js
I think the reason you are just seeing one element each time your components re-render is that in your map function on your allChars array, when you want to update the suggestions in your state, you are setting just the name each time as a new array while you should update the existing array in your state, so your code should be:
this.setState({
suggestions: [...this.state.suggestions, allChars.name]
})

How to update a node without overwriting all the child nodes with Google Cloud Function using a update Object?

I am updating "Rooms/{pushId}/ins" with a Google Cloud function that gets new In data from several "doors/{MACaddress}/ins".
The function currently goes like like this:
exports.updateRoomIns = functions.database.ref('/doors/{MACaddress}').onWrite((change, context) => {
const beforeData = change.before.val(); // data before the write (data of all the doors child nodes)
const afterData = change.after.val(); // data after the write (data of all the doors child nodes)
const roomPushKey = afterData.inRoom; // get the right room
const insbefore = beforeData.ins;
const insafter = afterData.ins; // get the after data of only the "ins" node
console.log(insafter);
if (insbefore != insafter) {
const updates = {};
The line directly above "const updates = {}" creates an empty object to be (later) populated, and to (later) update the "rooms/{roompushkey}/ins" node...
I think the problem may be here, on the "updates" const, as this object is redefined as a whole every time the function runs...
the code proceeds...
updates['/rooms/' + roomPushKey + '/ins'] = insafter; // populate the "INS" object with the values taken from the "/doors/{MACaddress/ins"
return admin.database().ref().update(updates); // do the update
} else {
return
}
});
This would work if I only had one door, but as I have several doors with different Ins data, every time I update a single "Door/{MACaddress/Ins", the whole "Rooms/{pushId}/Ins" gets replaced for whatever data is on the last updated door... I know the update method should be used for this purpose, and I kinda want to keep this "updates" object to fan out the data to other paths later. Is this possible? Any suggestions on how to solve this?
This is my data structure:
root: {
doors: {
111111111111: {
MACaddress: "111111111111",
inRoom: "-LBMH_8KHf_N9CvLqhzU", // I will need this value for the clone's path
ins: {
// I am creating several "key: pair"s here, something like:
1525104151100: true,
1525104151183: true,
}
},
222222222222: {
MACaddress: "222222222222",
inRoom: "-LBMH_8KHf_N9CvLqhzU", // I will need this value for the clone's path
ins: {
// I am creating several "key: pair"s here, something like:
2525104157710: true,
2525104157711: true,
}
}
},
rooms: {
-LBMH_8KHf_N9CvLqhzU: {
ins: {
// I want the function to clone the same data here:
1525104151100: true,
1525104151183: true,
}
}
}
Elaborating from my previous answer to your related question (How to clone a node to another path based on a reference value from the initial path on Google Cloud Functions?), I would adapt the code as follow:
exports.updateRoom = functions.database.ref('/doors/{MACaddress}').onWrite((change, context) => {
const afterData = change.after.val(); // data after the write
const roomPushKey = afterData.inRoom;
const ins = afterData.ins;
const updates = {};
Object.keys(ins).forEach(key => {
updates['/rooms/' + roomPushKey + '/ins/' + key] = true;
});
return admin.database().ref().update(updates);
}).catch(error => {
console.log(error);
//+ other rerror treatment if necessary
});
In other words, instead of replacing the full ins object, you add new children nodes in the existing ins node.

Categories

Resources