how to determine if my ractive computed value has changed - javascript

For my Layout I have a component which needs to be initialized once rendering is completed, and then again if anything in my data changes.
This would work great, but I rely on computed values for a filtered and changed output for each client and via observe the change event is fired to often. What I do:
let myRactive = new Ractive({
el: '#clientContainer',
template: myTemplate,
magic: true,
modifyArrays: true,
data: {data}, //=> this is an awful lot of Data changing all the Time
computed: {
usefulData(){
let tempdata = this.get('data');
//=> a lot of rearranging, filtering and sorting
// to adapt the data only for this specific client
return tempdata;
}
onrender: function () {
initmyLayoutComponent()
}
});
So I tried to get it this way
myRactive .observe( 'usefulData', function ( newValue, oldValue, keypath)
destroymyLayoutComponent();
initmyLayoutComponent();
});
But this fired a) every time anything in datachanges (even when it was something completely unrelated to usefulData), and b) before ractive has rendered the DOM so the component gets re-initialized to early.
Is there a way to observe only the computed value, or - which would be even better - just observe specific actions in the computed value (like I want to react to added/deleted Objects, but not to changed Values)?

Well what you can do, is to actually send in a clientData obj into the template instead, and then only listen to that data.
let myRactive = new Ractive({
el: '#clientContainer',
template: '<div>{{clientData.name}}</div><input type="text" value="{{clientData.name}}" /><div>{{email}}</div><input type="text" value="{{email}}" /><div>Clientdata changed: {{cnt}}</div>',
magic: true,
modifyArrays: true,
data: {
name: 'hallo',
email: 'a#a.com',
cnt: 0
}, //=> this is an awful lot of Data changing all the Time
computed: {
usefulData() {
let tempdata = this.get('name');
// Create your own data obj
tempdata = {
name: 'world'
};
// set it to the ractive context
this.set('clientData', tempdata);
}
},
oninit: function() {
this.observe('clientData', function(newValue, oldValue, keypath) {
let cnt = this.get('cnt')
cnt += 1;
this.set('cnt', cnt);
console.log('listen only to the computed data');
}, {init: false});
this.get('usefulData');
},
onrender: function() {
// do something
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/ractive/0.9.0-build-123/ractive.min.js"></script>
<div id="clientContainer"></div>

Related

VueJS: Replace/Update Array

I currently have an array of object that I am rendering to a table. I am trying to follow the examples provided by Vuejs to use a "single source of truth" shared between multiple vues on the same page.
Overall, I am trying to make it where when vue1.refresh() is triggered, all the vues update their data when the "single source of truth" is updated. However, self.surveys = surveys; only updates the data on vue1.
Note: I am following the guide from https://v2.vuejs.org/v2/guide/state-management.html
// The single source of truth
var cache = {
data: [{...}] // Array of objects
}
var vue1 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
refresh: function(){
var self = this;
// After getting data back from an ajax call
.done(function(surveys) {
self.surveys = surveys;
});
},
}
});
var vue2 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
// Methods
}
});
There are two principles of Vue that will help you here:
In Vue, every data item is a source of truth.
Only the owner of a data item should modify it.
In your example, you have three sources of truth: the one you want to be the single source, and two others that are initialized from it. Also, the one you want to be the source of truth isn't a data item, it is outside Vue.
So to start, you should have a single Vue that represents your entire application and defines any data that represents application-level state:
new Vue({
el: '#app',
data: {
cache: {
data: [...]
}
}
});
The two Vue objects that you created should be children of the application Vue, which is to say, components.
The parent tells the children what the truth is via props. The child can suggest changes to the truth by emitting events to the parent, but the child does not directly modify the truth. That keeps all management of the truth in one place.
You would need to mutate the array, not replace it.
Array.prototype.splice can do this for you, if you don't want to use something like Vuex, as suggested by Vanojx1.
Splice expects specific elements, not a complete array for insertions. Because you have an array you want to use and you need to clear the old one, the syntax is a little odd... You pass this, the start, the count to remove (the entire length), and then the elements to add (concatenated on from your new array).
Array.prototype.splice.apply([self.surveys, 0, self.surveys.length].concat(surveys));
Problem is, you are replacing shared Cache object previously assigned to surveys variable, with new, not shared object. And solution? Do not try to mutate cache object. Just use Vuex. Vuex is simple, real "Vue way" solution.
// The single source of truth
var cache = {
data: [{...}] // Array of objects
}
var vue1 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
refresh: function(){
var self = this;
// After getting data back from an ajax call
.done(function(surveys) {
self.surveys = surveys; // Problem is right here
});
},
}
});
var vue2 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
// Methods
}
});
Try this example, which works like you code - not correct way:
var cache = {
key1: 'Value1'
}
var vue1 = new Vue({
el: '#app1',
data: {
surveys: cache
},
methods: {
replace () {
this.surveys = {key1: 'Replaced'}
}
}
})
var vue2 = new Vue({
el: '#app2',
data: {
surveys: cache
},
methods: {
replace () {
this.surveys = {key1: 'Replaced'}
}
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app1">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
<div id="app2">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
Then try this example, with Vuex, where you can freely replace "cache object" and replacint will affect other instance:
const store = new Vuex.Store({
state: {
cache: {
key1: 'Value1'
}
},
mutations: {
replace (state) {
state.cache = {key1: 'Replaced'}
}
}
})
var vue1 = new Vue({
el: '#app1',
store,
computed: {
surveys () {
return this.$store.state.cache
}
},
methods: Vuex.mapMutations([
'replace'
])
})
var vue2 = new Vue({
el: '#app2',
store,
computed: {
surveys () {
return this.$store.state.cache
}
},
methods: Vuex.mapMutations([
'replace'
])
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex#2.4.0/dist/vuex.min.js"></script>
<div id="app1">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
<div id="app2">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
As said in the comment before, you can use vuex to accomplish what you need, everytime you need to pass data between diferent components you can do that with a eventBus or passing props up and down between the components.
When you have a aplication that needs to pass a lot of data and receive it you can use vuex, first you need to install it and then you can do it this way:
you should cut the methods out and place the mounted(), it fires when the component loads, i think it was you need
var vue1 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
}.
mounted() {
var self = this;
// After getting data back from an ajax call
.done(function(surveys) {
self.surveys = surveys;
});
}
});
when you get the response pass it to vuex store, you can do it with a mutation like this:
this.$store.mutation('handlerFunction', self.surveys)
in the vuex you need to have the handlerfunction inside the mutation
mutations: {
// appends a section to the tree
handlerFunction: (state, dataReceived) => {
//then you can do
state.surveys = dataReceived
},
then in your other component you can receive it via a getter, the logic is the same watch vuex for more deaills, you have the main logic of connection here.
Hope it helps!

Data object defined in created function but NOT reactive?

I have a Vue instance where data property is initialised as an object:
var app = new Vue({
el: '#app',
data: {
obj: { }
},
methods: {
},
created: function() {
this.obj["obj2"] = {}
this.obj["obj2"].count = 0
},
mounted: function() {
setInterval(function() {
this.obj.obj2.count++
console.log(this.obj.obj2.count)
}.bind(this), 1000)
}
})
<div id="app">
{{ obj['obj2'].count }}
</div>
And then when the instance is created I add a property to the obj.
However, when I want to display the object's object property count, it shows 0 and is not reactive. If I defined the whole object in the data, it is reactive but I can't define the object in the data because its data depends on an external source - API, that's why it is filled with data in created function.
The only way how I managed to make it show the current count is by forcing updates on the view but I don't think it's the correct solution.
Any suggestions?
The problem is that Vue can not track completely new properties on its reactive objects. (It's a limitation of JavaScript).
It's described in detail here: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
The short version is: You have to do
created: function() {
Vue.set(this.obj, 'obj2', {})
Vue.set(this.obj.obj2, 'count', 0)
}
or
created: function() {
Vue.set(this.obj, 'obj2', {
count: 0
})
}

Parent's data change does not update child component in vuejs

I have the following:
Vue.component('times-updated', {
template: '<span>Times Updated: {{ timesUpdated }}</span>',
data: function() {
return {
timesUpdated: this.$parent.myData.timesUpdated
}
}
});
var vm = new Vue({
el: '#test',
data: function() {
return {
myData: {}
}
}
})
setInterval(function(){
$.ajax({
url: `${window.location.href}/json`, // This just returns an array : array.timesUpdated: 2 etc
}).done(function (data) {
vm.myData = data; // changes this data
});
}, 1000)
and am using the following html:
<div class="test">
<times-updated></times-updated>
</div>
I poll a REST API that returns an array which includes a timesUpdated property:
{
timesUpdated: 5
}
My intention is that every second I use jQuery's $.ajax method to call the API, update the myData data object on vm, which would then update the times-updated component.
The code works on initial page load, the times-updated component can retrieve the value on its parent's myData property, but whilst I have confirms that vm.myData does reflect the new value from the API, the component doesn't update its display to show the new count.
What am i doing wrong?
The data function is only called once during the life cycle of the component; when it is initially created. So essentially your component is just displaying the value as it existed when the component was created.
Additionally, it's generally bad practice to reach out of a component to get a data value. Vue is props down, events up. You should convert your component to use a property.
Vue.component('times-updated', {
props:["times"],
template: '<span>Times Updated: {{ times }}</span>',
})
The fact that you are using a function to define the Vue in this particular case doesn't really matter, it's just not a typical practice. Components require a function because they need an isolated scope.
Here is an example.
That callback is required only in components
// vue instance
new Vue({
data: {
status: true
}
};
// vue components (callback)
Vue.component('custom-component', {
data: function() {
return {
status: false
}
}
});

Add a new Key: value pair into every existing array of a object JS, React.js

I'm new to react and learning it at a get go paste. I'm building a small PokemonApp that gets its data through the PokemonAPI and it render's it to the web.
The only data that I'm fetching through the API is the Pokemon name and URL of the Pokemon. The component that fetches the data is called getDataFromAPI and it uses axios (promises) to get it. The data is stored in the pokemoni array and is displayed on the browser. The state shown through the Crome's React Dew Tools is this.
State
pokemoni: Array[20]
0: {...}
name: "metapod"
url: "http://pokeapi.co/api/v2/pokemon/11/"
1: {...}
Now i wont to add a new [{key: value}] pair to all the existing objects that will change from false to true when the user clicks on a Pokemon that they got (it will be a button or something else). The state of the change by the user will be made only once (from false to true).
State
pokemoni: Array[20]
0: {...}
name: "metapod"
url: "http://pokeapi.co/api/v2/pokemon/11/"
havePokemon: "false"
1: {...}
The logic i came to was using .map() to add a new [{key: value}] pair to all the existing objects. And doesn't work. Because i don't understand the problem. The function is located inside the getDataFromAPI component and it is called updatePokemon
var PokedexApp = React.createClass({
componentDidMount: function(){
this.getDataFromAPI();
},
getDataFromAPI: function(){
var that = this;
PokemonAPI.getPokemonList().then(function (temp) {
that.setState({
pokemoni: temp,
isLoading: false
});
}, function (e) {
that.setState({
isLoading: true
});
});
//This function should update the array with a new key: value pair with the name havePokemon
var updatePokemon = this.state.pokemoni.map(function(myPokemon){
myPokemon.havePokemon= "false";
return myPokemon;
});
this.setState({pokemoni: updatePokemon});
},
componentWillUnmount: function() {
this.getDataFromAPI.abort();
},
getInitialState: function() {
return{
showMyPokemon: false,
searchPokemon: '',
pokemoni: [],
isLoading: false
};
},
render: function(){
var {pokemoni, showMyPokemon, searchPokemon} = this.state;
var filteredPokemoni = PokemonLocalStorageAPI.filterPokemone(pokemoni, showMyPokemon, searchPokemon);
return (
<div>
<h1 className="text-center page-title">PokedexApp</h1>
<PokedexAppSearch onSearch={this.handleSearch} />
<PokedexAppLista pokemoni={filteredPokemoni} onToggle={this.handleToggle}/>
</div>
)
}
});
module.exports = PokedexApp;
It does not work and i don't understand why. Ty in advance if u have the time to check my problem.

How to "react" on data changes with RxJS?

RxJS beginner here: I have problems with saving and tracking data changes using RxJS. Say I structure my app in small views/widgets and every view/widget has its own state and should do things on data changes. How do I do that?
More concrete example. Let's say I have a widget called Widget and Widget has a title and button. The state should contain the title and the information if the button was already clicked. From reading the docs of RxJS it seems this would be a good starting point:
var widgetState = new Rx.Subject().startWith({
wasClicked: false,
title: 'foo'
});
Now I want to be notified if some data changes:
var widgetStateChanges = widgetState.subscribe(function(data) {
console.log('data: ', data);
// what do i do with the data here?
// i would like to merge the new data into the old state
});
widgetStateChanges.onNext({ title: 'bar' });
I listen to the changes, but I don't know how to save them. I would also like to do special things, if a certain data change happens. Something like this.
widgetStateChanges.filter(function(e) {
return e.wasClicked;
}).do(function(e) {
console.log('Do something because was clicked now.');
});
However I can't filter a subscription (widgetStateChanges), only a subject (widgetState).
Use a BehaviorSubject to track observable state:
var widgetState = new Rx.BehaviorSubject({ wasClicked: false, title: 'foo' });
// change state, probably in response to UI events
// Note we always set the full state, not just the "delta"
widgetState.onNext({ wasClicked: true, title: 'foo2' });
// example listening to title input field and updating state
// assumes rxjs-jquery
$("#title").onAsObservable("change").subscribe (function (ev) {
var oldState = widgetState.value;
var newTitle = $("#title").val();
// do not mutate the oldState object, instead clone it and change the title
var newState = $.extend({}, oldState, { title: newTitle });
// send the update
widgetState.onNext(newState);
});
// listen to new state values, probably to update your HTML?
widgetState.subscribe(function (newState) { ... });
// listen only when wasClicked is true
widgetState
.filter(function (s) { return s.wasClicked; })
.subscribe(function (s) { ... });

Categories