My Vuex getter is not getting automatically updated.. how I can debug it? - vue.js

I'm using Vuex with a getter that filters a lot of data and then some components present it to the user grouped by status. The user can increment the visible count of elements per status by 5. How many items are visible currently is on the Vuex store and a getter uses this to create a "View object".
When I update this visibility object the getter is no rerun so something in the dependency tracking went south. I'm not adding or deleteing properties, still I'm using Vue.set(...) just to be sure.
This is the mutation that increments the visible amount of items for a status:
viewMore(state, status) {
console.log('viewMore')
const current = state.visibility.statuses[status]
Vue.set(state.visibility.statuses, status, current + 5)
}
This mutation is running well and I can see in the developer tools how the visibility increments reactively with every commit. Now here is the getter that depends on this data:
visibleProspects(state, getters) {
console.log('visibleProspects')
let result = {}
for (const status in getters.sourceData) {
if (!result[status]) {
result[status] = { prospects: [] }
}
getters.sourceData[status].forEach(function(prospect) {
if (result[status].prospects.length < state.visibility.statuses[status])
result[status].prospects.push(prospect)
})
}
return result
}
What this does is traverses a complex getter named sourceData (not shown here for brevity) and then depending on how many visible items there are it returns a new structure with that maximum in an array. visibleProspects is then used by my components and everything runs fine the first time or if a update the data that sourceData computes (e.g adding / editing / deleting a prospect).. but no matter what I do modifying state.visibility.statuses is not forcing visibleProspects to recompute.
How can I debug this?

You can make deep copy to make it reactive (using JSON.parse(JSON.stringify())
viewMore(state, status) {
console.log('viewMore')
const current = state.visibility.statuses[status]
state.visibility.statuses[status] = current + 5
state.visibility = JSON.parse(JSON.stringify(state.visibility))
}

#ittus 's answer should work. But the clone operation would be heavy if your state is big.
alternatively, you may try using Vue.set on the root state state.visibility instead. This should make the reactivity works as expected.
Vue.set(state.visibility, 'statuses', {
...state.visibility.statuses,
[status]: current + 5
})

Related

Apollo readFragment of optimistic response

What I try to do
I have an app which should work offline.
There is an Item-list. I can add an Item to this list with a mutation.
The update function of the mutation adds the Item to the Item-list. (Optimistic Response)
When I click on an Item, I want to see the details.
My Implementation
Content of Mutation update function:
const queryData = cache.readQuery<{ items: Item[] }>({
query: MY_QUERY,
variables: {
filter
}
});
if (!queryData?.items) {
return;
}
const newData = [...queryData.items, newItem];
cache.writeQuery({
query: MY_QUERY,
data: { items: newData },
variables: {
filter
}
});
Get details of the item in the vue-file:
apolloProvider.clients.defaultClient
.readFragment<Item>({
fragment: ITEM_FRAGMENT,
id:id
});
The problem
Adding the item to the Query-result works fine.
When I try to read the fragment:
I get null for items which were added by the Mutation update function
I get the expected object for items which were fetched from the backend
There is also the optimistic attribute in readFragment, but that doesn't make a difference.
Other observations
When I write and immediately read the fragment in the Mutation update function, I am able to get it.
cache.writeFragment({
fragment: ITEM_FRAGMENT,
data: item,
id: item._id,
});
const data = cache.readFragment({
fragment: ITEM_FRAGMENT,
id: item._id,
});
console.log({ data }); // This logs my item Object
Package versions:
{
"#nuxtjs/apollo": "^4.0.1-rc.3",
"apollo-cache-persist": "^0.1.1",
"nuxt": "^2.0.0",
}
Summary
apollo.readFragement doesn't work for values from an optimistic response.
Maybe someone here has an idea of what I am missing, or a different approach to implement this functionality
To get optimistic values from apollo cache you need to add true as second parameter in your readFragment call.
readFragment(options, optimistic)
(optimistic is false by default.)
In your case:
apolloProvider.clients.defaultClient
.readFragment<Item>({
fragment: ITEM_FRAGMENT,
id:id
}, true);
When creating an optimistic response for a resource that is in the process of being created via a mutation (e.g. your item), we need to assign some kind of temporary id to the optimistic data (see example in the docs). This is because the real resource hasn’t been created yet and we don’t know it’s id.
Given this, to read the optimistic response from the cache we need to use that same temporary id (as well as the __typename). When the mutation completes and we have the real response in the cache the optimistic response is discarded and we can use the real id.
I ran into this recently as I was assigning a temporary id, but not using it to retrieve the optimistic response from the cache to render the updated UI in that brief window where I was waiting for the mutation to complete.

Skip watcher in vueJS

I have a form for updating document entity.
The document entity consists of list of employees (which is an array of objects) and each employee has a post which is just a string.
I have a dropdown (kind of wrapper for vue-multiselect) which accepts the array of employees and syncs selected employee to a selectedEmployee variable in data().
And also I have a watcher for selectedEmployee which sets the post input automatically when an employee is selected in the dropdown.
So, when creating a document in the form everything's fine, however, when I update the document, then I fetch existing document from server, set selectedEmployee and set employee's post. But, the document also keeps employee's post, so the first time when I open document's form in order to update it, I don't want to automatically update document's post. I want it to be updated only when user actually selects employee himself.
But the watcher gets called the first time too.
So, imagine we have John Doe and his a manager. When I create the document, I change his post to designer. Then, I open up the document form in order to update it, and I should see that for this specific document John Doe's post is "designer", but the watcher gets called and returns the post to manager.
I tried to make a fake variable in data(), like doneFetching, but it works only if I update this var directly in watcher, which looks quite dangerous, plus, in other entities I have many different kinds of selected employees, so making tons of fake flags is not an option.
Here is real code sample (employee = representative in my case):
selectedApproveRepresentative(representative) {
if (!representative) {
this.memoData.approve_representative_id = null
return
}
this.memoData.approve_representative_id = representative.id
// Here is temporary solution, but I have many watchers for many different kinds of employees. If I move the doneFetching flag after I initialized the form, it'll be set to true, and only after that the watcher will be called
if (this.mode === 'update' && !this.doneFetching) {
this.doneFetching = true
return
}
// In normal case a representative might have or have not post, so depending on this case we set it to be empty or filled. But this should not be called the first time I open the form
this.memoData.approve_representative_post_dative_case =
representative.post_dative_case ?
representative.post_dative_case : ''
},
Here is where I initialize data:
created() {
if (this.memo) {
this.memoData = _.cloneDeep(this.memo)
this.selectedApproveRepresentative = _.cloneDeep(this.memo.approve_representative)
}
},
as I understood, your problem is the watcher executed when you init the component. Have you tried setting the immediate property of the watcher to false?
Not everybody knows that the watchers can be defined in different ways.
The simple one that everybody know
watchers: {
propertyToWatch() { //code... }
}
Passing the name of a function as 'string'
watchers: {
propertyToWatch: 'nameOfAfunctionDefinedInMethodsSection'
}
The object declaration
This one is the most descriptive way of declaring a watcher. You write it as an object with a handler property (it can be the name of a function passed as string as above), and other properties like deep to watch nested properties of an object, or in your case immediate which tells to the watcher if the should run immediately when the component is mounted.
watchers: {
propertyToWatch: {
immediate: false,
handler: function() { //code.. }
}
}

How to 'watch' newly created rows in local storage in VueJs?

I am working to create a grid like component in VueJs. You can see the JSFiddle here: https://jsfiddle.net/masade/nrb9f4j2/
In the above fiddle the <datacell> component is used to allow inline edit for any cell.
I am 'watch'-ing changes to the rows and saving them in local storage
A newly created row is being pushed to parent object by this.$parent.unshift(newrow) in the <addrow> component
The problem is when I add a new row, though it is being added to the rows local storage it is not being watched for changes
Steps to replicate the issue (on JS fiddle):
Click on Add Row and enter a name to add
Once the row is added, click on the second column (year) and update it
Any changes to newly added row is not being updated
However if you refresh the page, and modify the same again, I am able to update it again.
Please help me understand if I am doing something wrong
You stumbled into one of the change detection caveats in Vue. Vue cannot detect when you have added a property to an object using the index of that object.
You define newrow like this:
data: function(){
return {
newrow : {}
}
},
which means that newrow has no properties. After that, you always add properties to newrow using an index, like here:
this.$parent.cols.map(function(col,index){
if(typeof newrow[col.m] == "undefined")
newrow[col.m] = "";
})
This means that Vue doesn't know about any of the properties you just added.
I added a method to your fiddle called makeNewRow which adds the properties you need to newrow correctly using the $set method.
makeNewRow(){
this.newrow = {};
this.$parent.cols.map((col,index) => this.$set(this.newrow, col.m, null))
},
Then I call it in mounted and in your addRow method. This fixes your bug. Here is the updated fiddle.
Note: there was an additional bug in your code that I fixed. If someone opened your app and did not previously have grid-vue-local in their local storage, then the uid for gridStorage would be zero, and the first row they added would have the id 0. To fix this I simply added
save: function (rows) {
gridStorage.uid = rows.length
localStorage.setItem(STORAGE_KEY, JSON.stringify(rows))
}
data(){
return{
items:localStorage.fetch()
}
}
watch:{
items:{
handler:function(items){
localStorage.save(items);
},
deep:true
}
}
you can save in items,and watch items,if the items has changed,it will updata

extjs 4.1 how to reset the itemselector

I am using extjs 4.1.1a for developing some application.
I had a form consisting of two combo-boxes and an item-selector.
Based on the value selected in first combo-box , the itemselector will load its data from database. This is working fine.
My problem is, if i reselect the first combo-box the new data will be displayed in itemselector along with previous data displayed in itemseletor .That is previous data displayed in itemselector will remain there itself.
for example: name "test1" consists of ids 801,2088,5000. on selecting test1 in firstcombobox itemselector must show output as below.
and if "test2" consists of ids 6090,5040. on selecting test2 in firstcombobox itemselector must show output as below.
problem is. for first time if i select "test1" from firstcombobox , output will come as expected. if i reselect "test2" from firstcombobox , output will come as below.
as you can see, previous data displayed (marked in red rectagle) remains there itself with new data displayed (marked with green rectangle).
I want for every reselection of first combobox, previously displayed data in itemselector to be erased before printing new data on itemselector.
How can I reset the itemselector for every reselection of first combobox?
You should remove all items from the store of the itemselector by the removeAll command. After that you should load the store of the itemselector.
itemselector.store.removeAll();
itemselector.store.load();
Any solutions above solve my problem.
i found solution from Sencha Forum.
https://www.sencha.com/forum/showthread.php?142276-closed-Ext-JS-4.0.2a-itemselector-not-reloading-the-store
in the itemselector.js file, change the line marked below.
populateFromStore: function(store) {
var fromStore = this.fromField.store;
// Flag set when the fromStore has been loaded
this.fromStorePopulated = true;
// THIS LINE BELOW MUST BE CHANGED!!!!!!!!!!!!
fromStore.loadData(store.getRange()); //fromStore.add(store.getRange());
// setValue waits for the from Store to be loaded
fromStore.fireEvent('load', fromStore);
},
You need to insert...
this.reset();
at the head of the function that is inserting the data.
As an example...
Ext.override( Ext.ux.ItemSelector, {
setValue: function(val) {
this.reset();
if (!val) return;
val = val instanceof Array ? val : val.split(this.delimiter);
var rec, i, id;
for (i = 0; i < val.length; i++) {
var vf = this.fromMultiselect.valueField;
id = val[i];
idx = this.toMultiselect.view.store.findBy(function(record){
return record.data[vf] == id;
});
if (idx != -1) continue;
idx = this.fromMultiselect.view.store.findBy(function(record){
return record.data[vf] == id;
});
rec = this.fromMultiselect.view.store.getAt(idx);
if (rec) {
this.toMultiselect.view.store.add(rec);
this.fromMultiselect.view.store.remove(rec);
}
}
}
});
are u got it?
when u select that combobox frist stoe of item selector is null after store load with ur pass the para meters
for example
store.load(null),
store.proxey.url='jso.php?id='+combobox.getrawvalue(),
store.load();
like that so when ur select a value in ur combobox that time ur used a listeners to ur combobox in that listners ur used above code , select ur some value in combobox that time frist store is get null after ur pass some values to json.php then store load with responce so that time old data is remove and new data load in that store
if u post ur code i will give correct code
I ran into the same issue with ExtJS 4.2.1. I got it to work by calling reload() on the data store and then setValue() with an empty string on the item selector in the data store's reload() callback.
Ext.create("Ext.form.field.ComboBox", {
// Other properties removed for brevity
listeners: {
change: function(field, newValue, oldValue, eOpts) {
Ext.getStore("ExampleStore").reload({
callback: function() {
Ext.getCmp("ExampleItemSelector").setValue("");
}
});
}
}
});
Ext.create("Ext.data.Store", {
storeId: "ExampleStore",
// Other properties removed for brevity
});
Ext.create("Ext.form.FormPanel", {
// Other properties removed for brevity
items:[{
xtype: "itemselector",
id: "ExampleItemSelector",
// Other properties removed for brevity
}]
});
For any folks that are curious, I'm fairly convinced there's a bug in the item selector's populateFromStore() function. When the function is called, it blindly adds all of the values from the bound store (store) to the internal store (fromStore). I suspect there should be a call to fromStore.removeAll() prior to the call to fromStore.add(). Here's the relevant code from ItemSelector.js.
populateFromStore: function(store) {
var fromStore = this.fromField.store;
// Flag set when the fromStore has been loaded
this.fromStorePopulated = true;
fromStore.add(store.getRange());
// setValue waits for the from Store to be loaded
fromStore.fireEvent('load', fromStore);
},
EDIT 12/18/2013
If you've configured any callback events on the item selector (e.g. change), you may want to disable the events temporarily when you call setValue(""). For example:
var selector = Ext.getCmp("ExampleItemSelector");
selector.suspendEvents();
selector.setValue("");
selector.resumeEvents();
I had the same problem and finally I decided to modify the extjs source code, not considering it a big issue as extjs itself its saying in the start of the file
Note that this control will most likely remain as an example, and not as a core Ext form
control. However, the API will be changing in a future release and so should not yet be
treated as a final, stable API at this time.
Based on that, as jstricker guessed (and sadly I didn't read and took me a while to arrive to the same conclusion), adding fromStore.removeAll() before fromStore.add() solves the problem.
Outside of the problem (but I think it can be interesting as well), additionally, I also added listConfig: me.listConfig in the MultiSelect configuration (inside createList), that way it's possible to format each item additional options (such as images, etc.) setting in the 'itemselector' the option listConfig as it's explained in the (irrealistic) documentation.
Need to reset the store used in ItemSelector that can be done by setting Empty object like below. Also need to call clearValue() method of ItemSelector component.
store.setData({});
ItemSelectorComponent.clearValue();

Dojo Tree Refresh

Using Dojo 1.3, after adding a child (i.e. folder or item) to a tree, is there a way to have it reflected immediately via refresh or some other method?
From the official Dojo manual
Updating a Tree
People often ask:
how do I update a tree (adding or
deleting items?)
You can't update the
tree directly, but rather you need to
update the model. Usually the model is
connected to a data store and in that
case you need to update the data
store. Thus, you need to use a data
store that allows updates (through
it's official API), like
dojo.data.ItemFileWriteStore.
how do I refresh a Tree from the
store?
This isn't supported. The store
needs to notify the tree of any
changes to the data. Currently this is
really only supported (out of the box)
by dojo.data.ItemFileWriteStore, as
setting up a client-server dojo.data
source where the server notifies the
client whenever the data has changed
is quite complicated, and beyond the
scope of dojo, which is a client-only
solution.
Say, if your model has the query `{type:'continent'} - meaning any items with this property are top-level-items, then the following will model extension will monitor changes and refresh the tree's view
var dataStore = new ItemFileWriteStore( { ... });
new Tree({
store: dataStore,
model: new ForestModel({
onNewItem: function(item, parentInfo){
if(this.store.getValue(item, 'type') == 'continent'){
this._requeryTop();
}
this.inherited(arguments);
}
}
});
This should in turn call childrenChanged in the tree and update it every time a new item is added.
See model reference
As an addition, if the item added is not a toplevel item, an immediate update should be doable with this statement. parent is the treenode which has had an item added to its children.
tree._collapseNode(parent);
parent.state = 'UNCHECKED';
tree._expandNode(parent);
A more or less 'standard' refresh of the tree can be achieved by following. Reason for it not being added to base implementation, i think is because it will break the linkage with DnD features on a tree
dojo.declare("My.Tree", [dijit.Tree], {
// Close the store? (So that the store will do a new fetch()).
reloadStoreOnRefresh : true,
update: function() {
this.model.store.clearOnClose = this.reloadStoreOnRefresh;
this.model.store.close();
// Completely delete every node from the dijit.Tree
delete this._itemNodesMap;
this._itemNodesMap = {};
this.rootNode.state = "UNCHECKED";
delete this.model.root.children;
this.model.root.children = null;
// Destroy the widget
this.rootNode.destroyRecursive();
// Recreate the model, (with the model again)
this.model.constructor(this.model)
// Rebuild the tree
this.postMixInProperties();
this._load();
}
}
);
I've solved this WITHOUT needing the refresh.
_refreshNodeMapping: function (newNodeData) {
if(!this._itemNodesMap[newNodeData.identity]) return;
var nodeMapToRefresh = this._itemNodesMap[newNodeData.identity][0].item;
var domNode = this._itemNodesMap[newNodeData.identity][0].domNode;
//For every updated value, reset the old ones
for(var val in newNodeData)
{
nodeMapToRefresh[val] = newNodeData[val];
if(val == 'label')
{
domNode.innerHTML = newNodeData[val];
}
}
}