I have 2 components:
parent component (vue-bootstrap modal with a vue-bootstrap table)
child component (vue-bootstrap modal with a form)
When I submit the form in the child component I want to push the object to the parent table array and that works! But when I reset the form it also resets the object in the table array and I don't know why. I tried push and concat.
parent variable:
MA02_E_tb // table array [{descr_forn: '',fornitore:'',n_oda:''},{descr_forn: '',fornitore:'',n_oda:''}]
data() {
return {
form: {
descr_forn: 'prova',
fornitore:'prova',
n_oda:'prova',
}
},
methods: {
resetModal() {
this.form.descr_forn = '',
this.form.fornitore = '',
this.form.n_oda = '',
},
onSubmit: function(evt) {
evt.preventDefault()
this.$parent.MA02_E_tb = this.$parent.MA02_E_tb.concat(this.form)
},
result:
MA02_E_tb = [{descr_forn: 'prova',fornitore:'prova',n_oda:'prova'}]
When I reopen the child modal and I reset the object form with the resetModal method the result is:
MA02_E_tb = [{descr_forn: '',fornitore:'',n_oda:''}]
form = [{descr_forn: '',fornitore:'',n_oda:''}]
Why does it reset MA02_E_tb if it's another variable?
First up, you really, really, really shouldn't be using $parent like that. You should be emitting an event instead. But that isn't what's causing the problem.
The actual problem is that you are passing an object by reference. If you then change that object it will be changed in both places. It doesn't matter what name, identifier or path you use to access it, it's still the same object.
Assuming it is a flat object you can make a shallow copy using the spread operator, ...:
this.$parent.MA02_E_tb = this.$parent.MA02_E_tb.concat({...this.form})
This will create a new object with the same properties as this.form. It is important to realise that this is only a shallow copy. If this.form contained further reference types (i.e. objects, arrays, etc.) then those may need copying as well.
As an event this would be something like:
this.$emit('submit', {...this.form})
You'd then need a suitable #submit listener in the parent template to update the array. The idea here is that only the owner of the data should be allowed to modify it and in this case that array is owned by the parent.
Related
I am currently working with SvelteKit.
I have a derived store, which is necessary because it depends on another store. Now I need to change some values in the derived store directly. The problem is that derived stores are not modifyable as far as my understanding goes.
Is there any way to change the value of a derived store directly?
For example if I'd have a derived store called tiles which is an array of objects and I would like to change the property of one of its objects ($tiles[n].x = 'something new')
You can make a derived store writable by simply adding a set function.
You will need either access to the source object from which the value is derived or you need to merge the new value in a way that preserves the relevant object references. (Though in the latter case you may still need to do a dummy reassignment on the parent store, to trigger an update.)
E.g.
const parent = writable({ items: [{ name: 'pochi' }, { name: 'maru' }]});
const items = derived(parent, value => value.items);
items.set = newItems => $parent.items = newItems;
REPL
I'm struggling to understand how to dynamically create & populate a key: value pairs in an object in my state using Vue/Vuex, here's an example:
dataObject: {} (in state), and a mutation that creates the new key:value pairs:
setdataObjectProps: (state, payload) => {
for (let [key, value] of Object.entries(
state.dataObject
)) {
if (key == payload[0]) {
dataObject.total_operation_time = payload[1];
dataObject.machine_name = payload[2];
}
}
},
This solution works, but the key:value pairs should already be existing in the object (i've set them to empty strings).
I tried using Vue.set() like this:
Vue.set(dataObject.total_operation_time, payload[1]);
Vue.set(dataObject.machine_name, payload[2]);
However, i'm struggling to understand how to make it work since it expects second parameter that's the index/name, if i understand correctly. Can someone explain like i'm five how can i make it work without having to first create the key:value pairs in the object?
Thanks in advance!
P.S. They also have to be reactive.
Vue set should do the work only your using it the wrong way:
Adds a property to a reactive object, ensuring the new property is
also reactive, so triggers view updates. This must be used to add new
properties to reactive objects, as Vue cannot detect normal property
additions (e.g. this.myObject.newProperty = 'hi').
But the function arguments looks like this
{Object | Array} target
{string | number} propertyName/index
{any} value
https://v2.vuejs.org/v2/api/#Vue-set
In your case it should be:
Vue.set(state.dataObject, 'total_operation_time', payload[1]);
Vue.set(state.dataObject, 'machine_name', payload[2]);
I'm currently trying to add a React component into the third layer of my multidimensional array. My structure is as follows:
constructor(props) {
super(props);
/**
* The state variables pertaining to the component.
* #type {Object}
*/
this.state = {
structure : [ // Block
[ // Slot 1
[ // Layer 1
<Text>text 1</Text>, // Text1
]
]
],
};
}
this.state.structure contains an array for the slots, which contains an array for the layers which contains an array consisting of Text components.
I've tried to use both concat() and push() to try to set the structure array, but both give me errors like "cannot read property of undefined", etc. This is my current addText() function:
addText() {
this.setState((previousState) => ({
structure : previousState.structure[0][0].concat(<Text>text2</Text>),
}));
}
The addText() function is called upon pressing a button in my application.
I'm also rendering my array of Text components on the current layer directly through the array:
{ this.state.structure[0][0] }
I feel like I'm staring the problem directly in the face, but have no idea what's causing the issue. I'm expecting to be able to add another Text to the container, but nothing I've tried to do seems to be working.
For one thing, you really should NOT store instances of React components directly in state. Doing so is just asking for trouble down the line, as rendering, state updates, key management and data persistence get more and more difficult to wrangle.
Instead, store a model of your components in state, generally in the form of the props they should have. Then, at render time is when you translate those models into React components:
this.state = {
structure : [ // Block
[ // Slot 1
[ // Layer 1
"text 1", // Text1
]
]
],
};
render() {
return this.state.structure[0][0].map(ea => <Text key={ea}>{ea}</Text>);
}
Doing it this way is better because: what happens if you want to read or modify the contents of your "layer" arrays? If they are text values you can simply read/modify the text. But if they are instantiated React components... there is basically no way to effectively update your state without jumping through tons of hoops.
On to your specific problem: the issue is that when you evaluate a statement like this:
previousState.structure[0][0].concat(<Text>text2</Text>)
the concat() function actually returns the value of the array after concatenation. And the value of that array is (in this case) now [<Text>text 1</Text>, <Text>text2</Text>]. So, you are actually updating the value of your state.structure field to an entirely different array. I'm guessing the error you are seeing - "cannot read property of undefined" - is because when you try to access this.state.structure[0][0] you are trying to access it as if it were a two dimensional array but it is now in fact a one dimensional array.
As another replier put it much more succinctly: you are entirely replacing state.structure with the contents of state.structure[0][0] (after concatenation).
Updating deeply nested data structures in React state is super tricky. You basically want to copy all of the original data structures and change only parts of it. Fortunately, with ES6 "spread" operator we have a shorthand for creating new arrays with all of the same items, plus new ones:
addText() {
this.setState((previousState) => {
// Copy the old structure array
const newStructure = [...previousState.structure];
// Update the desired nested array by copying it, plus a new item
newStructure[0][0] = [...newStructure[0][0], "text2"];
this.setState({structure: newStructure});
});
}
Main takeaways:
Don't store React components in state! Store raw data and use it at render time.
Be careful about what you are updating state to - it needs to be the SAME state plus a few updates, not the result of some atomic operation.
Read up a bit on how array operations like concat() and push() actually work and what their return values are.
I dont agree with this arr structure, and hooks, with an object seems much more conventional, but it could be your this binding. as ray said below structure[0][0] is replacing the state.
if your using addText Method it needs to be bound to the class. If its not using arrow syntax, it will show as undefined as it is not bound, and will result in undefined. Hope it helps
for example
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
};
this.addText = this.addText.bind(this);
}
addText() {
this.setState((previousState, props) => ({
structure : previousState.structure[0][0].concat(<Text>text2</Text>),
}));
}
// ES6
addTextEs6 = () => {
this.setState((previousState, props) => ({
structure : previousState.structure[0][0].concat(<Text>text2</Text>),
}));
}
}
//ES6
/* Also you do not need to use concat you can use the spread Op. ... See MDN Spread Op`
Without getting into any philosophy about whether you should do it the way you're doing it, the fundamental problem is that you're replacing structure with structure[0][0] in your state.
{
// this line sets the whole structure to structure[0][0]:
structure : previousState.structure[0][0].concat(<Text>text2</Text>),
}
So I've got a vue component I'm using as an editing interface...
The database has a jsonified array inside of it (don't ask... I don't like it either), which has to be in a particular format... In order to allow admins to edit this string array without breaking the format, I'm making a vue editing component which will break the parts up into various textboxes etc...
I would like to have two separate variables - one for what the string array IS, and one for what they are changing... the first variable would be updated when they saved their changes...
The issue I have is that for some reason, when I update one of the variables... not only does the other variable change... but the prop changes as well...
I was under the impression that components could not change their props..?
In particular, the array looks something like this:
[
'1',
'2'
['1','2','3','4']
]
And when I do a .splice() on the sub-array, both variables and the prop updates...
Some sample code...
Laravel blade view:
<editor :somearray={{ $someJsonifiedArray }}></editor>
Component props/data setup:
props: {
somearray: {
default: [],
type: Array
}
}
data(){
return {
editedArray: this.somearray, // This is what will be saved
wipArray: this.somearray // This is what changes as they edit
}
}
Some of the methods:
resetChanges(){
this.wipArray = this.editedArray;
}
I'm probably missing something obvious... or misunderstanding how things work...
Javascript Array/Object is passed by reference, not by value!
if you do this
return {
editedArray: this.somearray, // This is what will be saved
wipArray: this.somearray // This is what changes as they edit
}
whenever you edit editedArray or wipArray, you are actually editing somearray, because they all refer back to the same array/Object .
So you gotta clone the array/Object instead of passing it directly. The simplest way to clone object is by using spread operator (or in some cases, deep cloning will be required). The simplest way to clone array is by using slice.
return {
editedArray: {...this.somearray}, // This is what will be saved
wipArray: {...this.somearray} // This is what changes as they edit
}
I want to add attribute to a JS object, but in a custom place, After a given attribute.
var me = {
name: "myname",
age: "myage",
bday: "mybday"
};
me["newAt"] = "kkk"; //this adds at the end of the object
Is there a way to specify the object (me), an attribute(age) in it and add a new attribute(newAt) right after the specified one? A better way than doing string operations?
var newMe = {
name: "myname",
age: "myage",
newAt: "newAttr",
bday: "mybday"
}
UPDATE: (Since people are more focused on why I'm asking this than actually answering it)
I'm working on a drawable component based on user input - which is a JS object. And it has the ability to edit it - so when the user adds a new property based on "add new node" on the clicked node, and I was thinking of adding the new node right after it. And I want to update the data accordingly.
JavaScript object is an unordered list of properties. The order is not defined and may vary when using with an iterator like for in. You shouldn't base your code on the order of properties you see in debugger or console.
JavaScript objects do, as of ES2015, have an order to their properties, although that order is only guaranteed to be used by certain operations (Object.getOwnPropertyNames, Reflect.ownKeys, etc.), notably not for-in or Object.keys for legacy reasons. See this answer for details.
But you should not rely on that order, there's no point to it, it's more complicated than it seems initially, and it's very hard to manipulate (you basically have to create a new object to set the order of its properties). If you want order, use an array.
Re your edit:
I'm working on a drawable component based on user input - which is a JS object. And it has the ability to edit it - so when the user adds a new property based on "add new node" on the clicked node, and I was thinking of adding the new node right after it. And I want to update the data accordingly.
The best way to do that is, if you want a specific order, keep the order of keys in an array and use that to show the object.
While you could use ES2015's property order for it, to do so you'd have to:
Require your users use a truly ES2015-compliant browser, because this cannot be shimmed/polyfilled
Destroy the object and recreate it adding the properties in the specific order you want each time you add a property
Forbid properties that match the specification's definition of an array index
It's just much more work and much more fragile than keeping the order in an array.
The simplest solution I could find was to iterate through the keys of the parent and keep pushing them to form a clone of the parent. But to additionally push the new object if the triggered key is met.
var myObj = {
child1: "data1",
child2: "data2",
child3: "data3",
child4: "data4"
};
var a = (function addAfterChild(data, trigChild, newAttribute, newValue) {
var newObj = {};
Object.keys(data).some(function(k) {
newObj[k] = data[k];
if (k === trigChild) {
newObj[newAttribute] = newValue;
}
});
return newObj;
})(myObj, "child3", "CHILD", "VALUE");
document.getElementById("result").innerHTML = JSON.stringify(a);
<p id="result"></p>