Vue Array converted to Proxy object - javascript

I'm new to Vue. While making this component I got stuck here.
I'm making an AJAX request to an API that returns an array using this code:
<script>
import axios from 'axios';
export default {
data() {
return {
tickets: [],
};
},
methods: {
getTickets() {
axios.get(url)
.then((response) => {
console.log(response.data) //[{}, {}, {}]
this.tickets = [...response.data]
console.log(this.tickets) //proxy object
})
},
},
created() {
this.getTickets();
}
};
</script>
The problem is, this.tickets gets set to a Proxy object instead of the Array I'm getting from the API.
What am I doing wrong here?

Items in data like your tickets are made into observable objects. This is to allow reactivity (automatically re-rendering the UI and other features). This is expected and the returned object should behave just like the array.
Check out the reactivity docs because you need to interact with arrays in a specific pattern or it will not update on the ui: https://v3.vuejs.org/guide/reactivity-fundamentals.html
If you do not want to have reactivity - maybe you never update tickets on the client and just want to display them - you can use Object.freeze() on response.data;

if you want reactive information use toRaw
https://vuejs.org/api/reactivity-advanced.html#toraw
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
or use unref if you donot want ref wrapper around your info
https://vuejs.org/api/reactivity-utilities.html#unref

You can retrieve the Array response object from the returned Proxy by converting it to a JSON string and back into an Array like so:
console.log(JSON.parse(JSON.stringify(this.tickets)));

You're not doing anything wrong. You're just finding out some of the intricacies of using vue 3.
Mostly you can work with the proxied array-object just like you would with the original. However the docs do state:
The use of Proxy does introduce a new caveat to be aware of: the proxied object is not equal to the original object in terms of identity comparison (===).
Other operations that rely on strict equality comparisons can also be impacted, such as .includes() or .indexOf().
The advice in docs doesn't quite cover these cases yet. I found I could get .includes() to work when checking against Object.values(array). (thanks to #adamStarrh in the comments).

import { isProxy, toRaw } from 'vue';
let rawData = someData;
if (isProxy(someData)){
rawData = toRaw(someData)
}

Related

RTKQ - Select data without hooks

I'm trying to select cached data from RTKQ without using the auto-generated query hook, but I'm having trouble understanding the docs
const result = api.endpoints.getPosts.select()(state)
const { data, status, error } = result
This is how the docs describe how to access the data, but I can't find any references on how to inject the state object "select()(state)".
I can't figure out how to access the data if I only call the select?
api.endpoints.getPosts.select()
Can someone explain me the difference between "select()" and "select()(state)"
Or what is the optimal solution to access the cached data from RTKQ?
The result of api.endpoints.getPosts.select() is a selector function for the result of using the "getPosts" endpoint without arguments.
Similarly, result of api.endpoints.getPosts.select({ page: 5 }) is a selector function for the result of using the "getPosts" endpoint the argument { page: 5 }.
A selector function is then called as selector(state) or passed into useSelector(selector).
If you write that altogether, you end up with api.endpoints.getPosts.select()(state).
#phry
Thank you for your answer! I'm not 100% sure I understood your answer. But it pointed me in a direction that enabled me to get the data.
I ended up creating a selector like the docs.
export const selectUser = (state) => userApi.endpoints.getUser.select()(state);
and in my function, I referenced it with getting the exported store from configureStore() method
const { data } = selectUser(store.getState());
But I'm not sure if this is the intended way to do it.

Vuex: store.state.activities has a key "list" that is an array of 3 items, however store.state.activities.list returns an empty array

My project is in Vue.
store.state.activities is an object that has 2 keys, one of them is an array called list that has 3 items.
However, when I try to reference it using store.state.activities.list, I get an empty array.
I've tried making both a shallow and a deep copy of store.state.activities, in both cases the copy has an empty list array.
store.state.activities structure:
{
"list": [
{
// some data
},
{
// some data
},
{
// some data
}
],
"dictionaries": {}
}
console.log(store.state.activities) - you can see list has 3 items:
whereas store.state.activities.list returns an empty array:
the usual reasons for this are you've not initialised or accessed the vuex correctly
initialising
is usually done in your main file and should look something like
import { store } from "../store";
createApp(App)
.use(store)
.mount("#app");
accessing
if you are using the Options API you have to access the store via the this in the components, which looks as so
this.$store.state.activities
if you are using the composition API however the this object is not available in which case you should do something like
import {useStore} from "vuex"
...
setup(){
const store = useStore();
return {
activities : computed(()=>store.state.activities),
...
if you are just importing the store object you have defined then you will be getting a uninitialised version that wont be picking up any changes you make
if using type script the code is a little different
the final posibility i can think of is your assignment to the list property
if you are doing
this.$store.state.activities.list = [{},{}.{}];
then you will be replacing the list which will break the reference, assignment should be done via a mutator as vuex will wrap these changes in reactive wrapers that tell watchers of changes
mutations: {
setList(state, value) {
state.activities.list = value;
},
...
},
which would then be called as
store.commit("setList", [{},{},{}]);

Storing JSON structure in VueX

I'm fairly new to Vue/VueX, and I'm researching a solution to store JSONs in the VueX state. At first it seems pretty straightforward:
state {
jsonthing: { ... }
}
The problem: getters return Observer type, not Object type. So I can do this and retrieve the entire JSON structure:
getters: {
getJSON(state) {
return state.jsonthing;
}
}
But I can't retrieve a node or a single value of the JSON, like this:
getters: {
getOneNode: state =>
nodeName => {
return state.jsonthing[nodeName];
}
}
}
The getter retrieves state.jsonthing as an Observer. I can't find a way to extract the desired content of the JSON from this Observer and return that. I know I can do it in my components using mapState but that's not what I'm looking for. Is there a way to do it in the getter?
What I'm currently doing is that I store the JSON as a string (JSON.stringify()) and convert it back in the getter (JSON.parse()). Strings are retrieved as strings, and not Observers. It works, but it's a hack.
While we are here, I also can't find any documentation for the Observer type. I'd appreciate if someone could drop a URL!
All right guys, you're not very active today, so here is a solution. If you know a better one, please let me know.
Solution 1:
Convert the JSON into a string with JSON.stringify() and store it as a string. Then the getter converts it back with JSON.parse(). This works, but admittedly ugly.
Solution 2:
Generate a function that returns the JSON. This is way better.
state: {
jsonthing: null
}
mutations: {
INITIALIZE(state, jsonthing) {
state.jsonthing = new Function(`return ${ JSON.stringify(jsonthing) }`)
}
}
getters: {
getOneNode: state =>
nodeName => {
return state.jsonthing()[nodeName];
}
}
}
I've written an article on Medium about this method and a practical application (which I'm actually implementing).
https://medium.com/developer-rants/simple-dynamic-localization-in-vue-with-vuex-b429c525cd90

Updating Object Property in Reducer without Mutation

I feel like my reducer should be working, but it keeps insisting that I'm mutating the state.
Uncaught Error: A state mutation was detected inside a dispatch, in the path: output.outputList.0.composition. Take a look at the reducer(s) handling the action {"type":"SET_OUTPUT_COMPOSITION",
I posted something similar a couple hours ago with no answers, but I figured my redux state was too complicated. This is my simplified version and I'm still getting mutate errors.. what am I doing wrong? should I not be using a class in my redux state? should i be using some sort of immutable library? please help me.
My Initial Redux State
output: {
outputList: [], //composed of Output class objects
position: 0
}
Output Class
class Output {
constructor(output) {
this.id = output.id;
this.composition = output.getComposition();
this.outputObj = output;
this.name = output.name;
this.url = output.getUrl();
}
}
export default Output;
Reducer for updating property
case types.SET_OUTPUT_COMPOSITION: {
let outputListCopy = Object.assign([], [...state.outputList]);
outputListCopy[state.position].composition = action.composition;
return Object.assign({}, state, {outputList: outputListCopy});
Action
export function setOutputComposition(comp) {
return { type: types.SET_OUTPUT_COMPOSITION, composition: comp}
}
The spread operator does not deep copy the objects in your original list:
let outputListCopy = Object.assign([], [...state.outputList]);
It is a shallow copy, therefore
outputListCopy[state.position].composition = action.composition;
You are actually mutating previous state objects, as you said in your comment there are several ways to work around this, using slice/splice to create new instance of the array, etc.
You can also take a look at using ImmutableJS, in general I would say storing classes in the redux store makes the thing a bit hard to understand, I tend to favor simple structures that can be easily inspected with redux-tools.
The error is coming from dispatch. So it not even getting as far as the reducer. I expect it does not like you using class to define output. Instead just do const output ={ ... }.

Vue.js - Add or set new data to array in store

I have two Vue components that use a common array set in a store like this:
var store = {
state: {
myArray: []
}
};
var FirstComp = Vue.extend({
template: '#first-template',
data: function () {
return {
arrayData: store.state.myArray
};
}
});
/* A second component quite identical */
I followed the example given in the Vue js guide.
I'm trying to update the data in the array in the store with new data from another array (after an ajax call), so that it impacts both components. I would like to have a nice way of replacing / concating the old array with a new one. I know I can't just replace the array like this store.state.myArray = newArrayData;because I would loose the Vue binding. But the method given in the docs (at least for concat) doesn't work in the case of the store (or maybe I'm missing something?).
Right now, the only way I've found is to use a foreach with push, $removeor $set depending on the operation and it is not that elegant and practical.
For example, for concat, I do this:
$.each(newArray, function (i, val) {
store.state.myArray.push(val);
});
But for replacing it gets uglier. What would be the proper way to this?
(For info, I'm not using Vuex for state management and I don't plan to at the moment, I'm keeping it very simple)
To make the assignment work you can just use "state" in your component like this:
var FirstComp = Vue.extend({
template: '#first-template',
data: function () {
return {
state: store.state
};
}
});
And then use state.myArray. This way if you will do store.state.myArray = newValue it won't break the binding.

Categories

Resources