Vue3 reactive array from a get defined property? - vuejs3

I have an object set such as :
user = {
name: 'Bill',
addresses: [1, 2, 3]
}
addresses = {
1: { zipcode: '91802', city: 'Los Angeles', country: 'US' },
2: { zipcode: '06390', city: 'New York', country: 'US' },
3: { zipcode: '94301', city: 'Palo Alto', country: 'US' }
}
I modify the object such as :
user._addresses = user.addresses
Object.defineProperties(document, {
addresses: {
get () {
return user._addresses.map(id => addresses[id])
}
}
})
The issue is that if I now update _addresses, the addresses object is not automatically updated.
Such as:
user._addresses = [1, 2]
The interface does not update to reflect the changes.
I tried to play with ref, toRef, reactive from Vue3 but I honestly don't know where to place them in order to have a reactivity on addresses whenever I change _addresses.

Found it!
The issue is that a Proxy works on an array as long as it is not overriden.
So doing
proxy_array = []
Will erase the proxy and replace it with a basic array!
So far, the best way I found to keep the Proxy (and thus, the reactivity) is to clear the array then add the items back, one by one:
proxy_array.splice(0, proxy_array.length)
new_array.forEach(item => proxy_array.push(item))
If there's a better solution for that, I'm interested!

Related

How to structurally compare the previous and new value of nested objects that are being used in `watch`, in Options API?

I have a question which is a mix of both composition API and options API
What I want to do: I want to watch an object. That object is deeply nested with all kinds of data types.
Whenever any of the nested properties inside change, I want the watch to be triggered.
(This can be done using the deep: true option).
AND I want to be able to see the previous value and current value of the object.
(this doesn't seem to be possible because Vue stores the references of the objects, so, now the value and prevValue point to the same thing.)
In Vue3 docs, for the watch API, it says this
However, watching a reactive object or array will always return a reference to the
current value of that object for both the current and previous value of the state.
To fully watch deeply nested objects and arrays, a deep copy of values may be required.
This can be achieved with a utility such as lodash.cloneDeep
And this following example is given
import _ from 'lodash'
const state = reactive({
id: 1,
attributes: {
name: ''
}
})
watch(
() => _.cloneDeep(state),
(state, prevState) => {
console.log(state.attributes.name, prevState.attributes.name)
}
)
state.attributes.name = 'Alex' // Logs: "Alex" ""
Link to docs here - https://v3.vuejs.org/guide/reactivity-computed-watchers.html#watching-reactive-objects
However, this is composition API (if I'm not wrong).
How do I use this way of using cloneDeep in a watch defined in options API?
As an example, this is my code
watch: {
items: {
handler(value, prevValue) {
// check if value and prevValue are STRUCTURALLY EQUAL
let isEqual = this.checkIfStructurallyEqual(value, prevValue)
if (isEqual) return
else this.doSomething()
},
deep: true,
},
}
I'm using Vue 3 with Options API.
How would I go about doing this in Options API?
Any help would be appreciated! If there's another way of doing this then please do let me know!
I also asked this question on the Vue forums and it was answered.
We can use the same syntax as provided in the docs in Options API using this.$watch()
data() {
id: 1,
attributes: {
name: ''
}
}
this.$watch(
() => _.cloneDeep(this.attributes),
(state, prevState) => {
console.log(state.name, prevState.name)
}
)
this.attributes.name = 'Alex' // Logs: "Alex" ""

Updating array within array redux

I've got state with a nested array that looks like the following:
{
list: [
{
id: '3546f44b-457e-4f87-95f6-c6717830294b',
title: 'First Nest',
key: '0',
children: [
{
id: '71f034ea-478b-4f33-9dad-3685dab09171',
title: 'Second Nest',
key: '0-0
children: [
{
id: '11d338c6-f222-4701-98d0-3e3572009d8f',
title: 'Q. Third Nest',
key: '0-0-0',
}
],
}
],
],
selectedItemKey: '0'
}
Where the goal of the nested array is to mimic a tree and the selectedItemKey/key is how to access the tree node quickly.
I wrote code to update the title of a nested item with the following logic:
let list = [...state.list];
let keyArr = state.selectedItemKey.split('-');
let idx = keyArr.shift();
let currItemArr = list;
while (keyArr.length > 0) {
currItemArr = currItemArr[idx].children;
idx = keyArr.shift();
}
currItemArr[idx] = {
...currItemArr[idx],
title: action.payload
};
return {
...state,
list
};
Things work properly for the first nested item, but for the second and third level nesting, I get the following Immer console errors
An immer producer returned a new value *and* modified its draft.
Either return a new value *or* modify the draft.
I feel like I'm messing up something pretty big here in regards to my nested array access/update logic, or in the way I'm trying to make a new copy of the state.list and modifying that. Please note the nested level is dynamic, and I do not know the depth of it prior to modifying it.
Thanks again in advance!
Immer allows you to modify the existing draft state OR return a new state, but not both at once.
It looks like you are trying to return a new state, which is ok so long as there is no mutation. However you make a modification when you assign currItemArr[idx] = . This is a mutation because the elements of list and currItemArr are the same elements as in state.list. It is a "shallow copy".
But you don't need to worry about shallow copies and mutations because the easier approach is to just modify the draft state and not return anything.
You just need to find the correct object and set its title property. I came up with a shorter way to do that using array.reduce().
const keyArr = state.selectedItemKey.split("-");
const target = keyArr.reduce(
(accumulator, idx) => accumulator.children[idx],
{ children: state.list }
);
target.title = action.payload;

Denormalize with normalizr

I am trying to denormalize some data from the redux state, but I can't get it to work. I have tried putting together the denormalize function in all ways I can think. It is a guessing game and nothing have worked so far.
First I normalize the product data and put it into the state.
The entities are being put in state.data.products, including the products.
state.data: {
products: {
products: {
9: {
id: 9
attributes: [ 10, 45, 23 ],
things: [23, 55, 77]
},
11 //etc
}
attributes: {
10: {
id: 10
},
45: //etc
},
things: { //etc
}
}
So there is one property "products" that includes all entities, and there is another "products" for the "products" entity.
The normalizing works without problem:
normalize(data, [productSchema]);
The "productSchema" is defined like this:
productSchema = new schema.Entity('products', {
attributes: [attributeSchema],
things: [thingsSchema],
//+ other stuff
});
Now I want to denormalize a product, for example 9.
I use "connect" in a view component to call this function to retrieve the data from state and denormalize it:
export const denormalizeProduct = (state, productId) => {
const entities = state.data.products;
return denormalize(
productId,
[productSchema],
entities
);
}
If I call denormalizeProductById(state, 9) , I only get 9 back.
(similar to this question , but I couldn't find a solution from it, and it mixes together two examples)
If I put the "productSchema" without the array:
return denormalize(
productId,
productSchema,
entities
);
I get the error: TypeError: entities[schemaKey] is undefined.
Everything I have tried results in either "9" or the above error.
Edit:
found the error - added it below
I found the error - one of my schemas was wrong: I had created new schema.Entity('shippingZones') but in the reducer I added it to the state with a different name:
shipping_zones: action.payload.shippingZones
So it was looking for "shippingZones" while there was only "shipping_zones" I guess. So I changed it to new schema.Entity('shipping_zones') and shipping_zones: action.payload.shipping_zones
It's a pity that the message TypeError: entities[schemaKey] is undefined couldn't have specified "shippingZones" , then I could have saved a few days time.
So this code worked in the end:
const entities = state.data.products;
return denormalize(
productId,
productSchema,
entities
);

Twitter Typeahead and Handlebars

I'm having a bit of trouble implementing an idea I had to make twitter's typeahead suggestion property a little dynamic.
I have the following piece of code:
$(inputElement).typeahead({
highlight: true,
hint: false
}, {
name: 'engine',
limit: 15,
displayKey: config.displayKey,
source: config.engine.ttAdapter(),
templates: {
empty: [
'<div class="empty-suggestion-message">',
'No results matched your query.',
'</div>'
].join('\n'),
suggestion: Handlebars.compile('<p> <span>{{name}}</span> - <span>{{email}}</span> - <span>{{phone}}</span> </p>'),
}
});
In the Handlebars.compile part, I'm printing the source's response keys, name, email and phone. What I want to do is, having set an object to hold some config:
var _setConfig = function (url, query, displaykey, suggestion) {
config.url = url;
config.query = query;
config.displayKey = displaykey;
config.suggestion = suggestion;
console.log(config.suggestion);
};
I would like to change the suggestion part to:
suggestion: Handlebars.compile('<p> <span>{{config.suggestion[0]}}</span> - <span>{{config.suggestion[1]}}</span> - <span>{{config.suggestion[2]}}</span> </p>')
Assuming I pass in an array (e,g ['name', 'email', 'phone']) as the suggestion parameter to the _setConfig() function.
Any ideas ??
Thanks :D

creating dojo datagrid programmatically "sorry an error occured"

I am trying to create a Dojo dojox.grid.DataGrid programmatically. It is getting created but does not show the output, instead it shows an error with the message: "Sorry, an error occurred"
Here is the code:
var info=[
{
"Propery":"key1","Value":"value1"},
{
"Property":"key2", "Value":"value2"},
{
"Property":"key3","Value":"value3"}
];
var layout = [
{
name: "Property",
field: 'Property',
width: "180px"},
{
name: "Value",
field: 'Value',
width: "180px"}
];
var myStore = new dojo.data.ItemFileReadStore({data:{items:info}});
var grid = new dojox.grid.DataGrid({
store: myStore,
structure: layout
},"myDiv");
grid.startup();
I don't know where it is going wrong, please help me out.
Dude, this is like the third time I answer a question of yours. It strongly reminds me of the two before, but i'll have a try anyway.
Your example can't work, because in your layout-variable, you refer to a field named 'property' which doesn't exist in data.items (info).
You also did't get the concept of the json-array right. It schould represent like a bunch of object with a very similar structure. Assume you want to store some people, then you would have some keys like firstname, lastname, age, gender etc. Each person would have different values on this. The json-array would look like that:
var people = [
{
firstname: 'maja',
lastname: 'van hinten'
age: 23
gender: 'w'},
{
firstname: 'willy',
lastname: 'wantstoknowit'
age: 11
gender: 'm'},
{
firstname: 'helmut',
lastname: 'kohl'
age: 101
gender: 'm'},
];
Note, that the names of the properties are similar, just the values differ.
What you try to do obviously is, to store one single object as an array, and make each property an object. Think about it: [] means array, {} means object in JavaScript.
In fact, your info-variable should look like this:
var info = [
{
"key1": "value1",
"key2": "value2",
"key3": "value3"}
];
and if you would like to store some more object it would look like this:
var info = [
{
"key1": "value1",
"key2": "value2",
"key3": "value3"},
{
"key1": "value4",
"key2": "value5",
"key3": "value6"},
{
"key1": "value7",
"key2": "value8",
"key3": "value9"}
];
... objects stored in an array. Simple as that.
Now to your layout var:
It should discribe the structure of your DataGrid in relation to the data given by its store. So it descripes the columns as a whole, not the single cells.
You can therefore only refer to the property-names (key1, key2, key3, or firstname, lastname, age, gender) but not to the values, as the may be different sometimes. You only know the structure of your objects, not its actual content.
Therefore, it should look like that:
var layout = [
{
name: "Key1's content", // you can name this whatever you want
field: 'key1', // but not this, it refers to the property name of your objects
width: "180px"},
{
name: "Key2's content", // this could be named randomly too of course
field: "key2",
width: "180px"},
{
name: "Key3's content", // and that one as well
field: "key3",
width: "180px"}
];
Not sure if this will help, but in the example code you posted, you misspell the word 'Property' in one of your item, while the other two items have the word 'Property' spelled correctly. Refer to item with property key1. You have to make sure all your field names are spelled consistently.

Resources