I have an array of objects that I am binding to the DOM via a ValueConverter. Aurelia can't quite figure out what I'm doing in the ValueConverter, and so it isn't updating properly. I want to force dirty checking on this object. How can I do that?
Expose your array via a property getter.
Instead of:
export class Foo {
myArray = []; // will be observed without dirty-checking
}
Use a property getter:
export class Foo {
_myArray = []; // internal value that you will manipulate as-needed
get myArray() { // this property will be dirty-checked.
return this._myArray;
}
}
Related
HTML:
<comp-two></comp-two>
JS:
class CompTwo extends HTMLElement {
constructor() {
super()
this._options=[]
}
get options() {
return this._options
}
set options(val) {
this._options = val
}
}
const el = document.querySelector('comp-two')
el.options = ['one','two','three']
Is there an accepted method for notifying the code inside the webComponent that a property has been set? If it makes any difference I'm nesting web components.
I can see that setting an attribute would do it, but seems wasteful?
A property can be read directly:
console.log(this.propertyName)
But as the value could change any time after the component is created, the problem is to know when a change occurs, similar to how attributeChangedCallback is used to act on attribute updates.
A setter will trigger when the named property is written to. Just like attributeChangedCallback for attributes, the property's value has not necessarily changed.
set propertyName(val){
this._propertyName = val // keep a copy, name must be different
// underscore is popular method
this.doSomething(val) // act on the new property value
}
get propertyName(){
return this._propertyName
}
Note: After including a setter the property can no longer be read directly. Use a getter to return the local copy saved by the setter, or just read the local copy directly.
The penny finally dropped for me..
Suppose I'm using an external API which works with Machine objects. You can create a Machine with createMachine, which will give you a complex object with several nested properties, and a set of functions to alter state on that object. The API provides for example: loadMemory, sleep, connectDevice. (Imagine anything similar, this is just an example).
I want to mantain a global Vuex Machine object, so I've an action to dispatch that initial creation and store the returned object just like:
actions: {
createChannel({ commit, state }, params) {
m.createMachine(params).then(
function (newMachine) {
commit('setMachine', newMachine);
}
).catch(err => { console.error("Error"); } )
}
}
The mutation is pretty straightforward in this case:
setMachine(state, machine) {
state.machine = machine;
}
Now that API set for the "Machine" objects, as we know has a bunch of state-modifying calls -we don't know what specific fields they change-.
As they modify state, and I want to use them to affect the global Machine object in the Vuex store, I would like to wrap them in actions.
An action could call
this.state.machine.loadMemory(mem.addr)
But if this call itself modified the machine object, how do I commit the new state? Should I clone the old object, apply the state-changing method and replace the object ?
I know cloning is not an easy task.
Thanks.
You can re-mount your complex object. According to the example, the mutation could be:
loadMemory(state, newAddr) {
const { machine } = state;
state.machine = {
...machine, // set all machine's properties
addr: newAddr,
};
}
It works in any level of nested objects you want. Another example:
loadMemory(state, newValue) {
const { machine } = state;
const { machineObjProp } = machine;
state.machine = {
...machine, // set all machine's properties
machineObjProp: {
...machineObjProp, // set all machineObjProp's properties
value: newValue,
},
};
}
One way is using lodash cloneDeep, it will copy app properties and methods of object
import _ from lodash
this.state.machine.loadMemory(mem.addr)
const copyMachine = _.cloneDeep(this.state.machine)
this.$store.commit('setMachine', copyMachine)
I have a function getBar() returning an object like:
{
foo: 'value',
array: ['a', 'b', 'c']
}
Here's my React component calling the above function i.e. getBar():
class foo extends Component {
state = {
bar: {}
};
componentDidMount(){
this.setState({
bar : getBar()
});
}
render() {
{this.state.bar.array.map((value, i) => <div class="row" key={i}>{value}</div>)}
}
}
It always gives me Uncaught TypeError: Cannot read property 'map' of undefined error. Exploring similar questions, I came to know I will have to declare an empty state array which I did in different ways but none worked. Can anybody please give me an appropriate answer preferably with complete logic.
I tried another way of declaring the state array to a const in render() but didn't get successful results.
Ok, so this is actually something to do with your component's lifecycle
The problem is that your render method runs before the componentDidMount method. So the first time your component renders your state looks like this:
{
bar: {},
}
So no array property on bar, which means you cannot map over it (which is why you get errors 😣). Instead you could use the componentWillMount method, to set the state before the render method runs, or you could do a check on array being set before mapping over it.
This is weird. What am I doing wrong?
class Store extends Riot.Observable {
trigger():void {
// shouldn't this be completely overwriting the trigger method on riot.observable?
console.log("my trigger....");
}
}
let store = new Store();
store.trigger();
Expected behaviour: "my trigger...." in the console. What I get is the original implementation of trigger on the Riot.Observable, which errors because of no parameters being passed.
If I poke the store object I can see on store.__proto__ does have trigger on there, with my implementation. But store iself has its own (original) copy of trigger()
Please see https://jsfiddle.net/sidouglas/5spbvpnn/
I referenced this with a very basic example, and I don't know what's going on.
Based on the source, riot observables do not take advantage of prototypical inheritance. They work as mixins instead. The typescript wrapper class just calls the original riot mixin. To overwrite a function, you have to assign it to the instance:
class Store extends Riot.Observable {
constructor() {
this.trigger = function() {
console.log("My trigger");
};
}
}
let store = new Store();
store.trigger();
When i create several instances of a react class (by using React.createElement on the same class), some member variables are shared between the instances (arrays and objects are shared, strings and booleans etc. not).
For me this feels horrible and scary and wrong. Is this a bug or is there another way to do what i want to do?
Please have a look:
http://jsbin.com/kanayiguxu/1/edit?html,js,console,output
What you should be doing is setting state on your component, instead of having state as arbitrary properties on your React component.
So instead of doing this:
var MyComponent = React.createClass({
myArray: [1, 2, 3],
componentWillMount() {
this.myArray.push(this.myArray.length + 1);
},
render() {
return (
<span>{this.myArray.length}</span>
);
}
});
You should be doing this:
var MyComponent = React.createClass({
getInitialState() {
return {
myArray: [1, 2, 3]
};
},
componentWillMount() {
this.setState(state => {
state.myArray.push(state.myArray.length + 1);
return state;
});
},
render() {
return (
<span>{this.myArray.length}</span>
);
}
});
The reason being that all of a components state and data should reside in this.state and this.props which is controlled and handled by React.
The benefit you get from using props and state for this, is that React will know when those change, and from that it can tell when it's time to re-render your component. If you store state as arbitrary properties or globals, React won't know when those change, and cannot re-render for you.
The reason for the behaviour you're seeing is that every instance of the component uses the object you give to React.createClass() as its prototype. So all instances of the component has a myArray property, but that is on the prototype chain, and thus shared by all instances.
If you truly want something like this and you want to avoid this.state, you should use something like componentWillMount and inside that method, assign properties to this. This will make sure that such data is only on that particular instance, and not on the prototype chain.
EDIT
To even further clearify, it can be good to know that the object passed to React.createClass() isn't the actual object on the prototype. What React does is that it iterates over all properties on that object, and copies them onto the prototype of the React element object. This can be illustrated by this example:
var obj = {
myArray: [1, 2, 3],
title: 'My title',
componentWillMount() {
this.myArray.push(this.myArray.length + 1);
},
render() {
return (
<span>{this.myArray.length}</span>
);
}
}
var MyComponent = React.createClass(obj);
// This doesn't change the component, since 'obj' isn't used anymore
// by React, it has already copied all properties.
obj.title = 'New title';
// This however affects the component, because the reference to the array
// was copied to the component prototype, and any changes to what the
// reference points to will affect everyone who has access to it.
obj.myArray.push(666);