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

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", [{},{},{}]);

Related

Vuex/Redux store pattern - sharing single source of data in parent and child components that require variations of that data

I understand the benefits of using a store pattern and having a single source of truth for data shared across components in an application, and making API calls in a store action that gets called by components rather than making separate requests in every component that requires the data.
It's my understanding that if this data needs to change in some way, depending on the component using the data, this data can be updated by calling a store action with the appropriate filters/args, and updating the global store var accordingly.
However, I am struggling to understand how to solve the issue whereby a parent component requires one version of this data, and a child of that component requires another.
Consider the following example:
In an API, there exists a GET method on an endpoint to return all people. A flag can be passed to return people who are off sick:
GET: api/people returns ['John Smith', 'Joe Bloggs', 'Jane Doe']
GET: api/people?isOffSick=true returns ['Jane Doe']
A parent component in the front end application requires the unfiltered data, but a child component requires the filtered data. For arguments sake, the API does not return the isOffSick boolean in the response, so 2 separate requests need to be made.
Consider the following example in Vue.js:
// store.js
export const store = createStore({
state: {
people: []
},
actions: {
fetchPeople(filters) {
// ...
const res = api.get('/people' + queryString);
commit('setPeople', res.data);
}
},
mutations: {
setPeople(state, people) {
state.people = people;
}
}
});
// parent.vue - requires ALL people (NO filters/args passed to API)
export default {
mounted() {
this.setPeople();
},
computed: {
...mapState([
'people'
])
},
methods: {
...mapActions(['setPeople']),
}
}
// child.vue - requires only people who are off sick (filters/args passed to API)
export default {
mounted() {
this.setPeople({ isOffSick: true });
},
computed: {
...mapState([
'people'
])
},
methods: {
...mapActions(['setPeople']),
}
}
The parent component sets the store var with the data it requires, and then the child overwrites that store var with the data it requires.
Obviously the shared store var is not compatible with both components.
What is the preferred solution to this problem for a store pattern? Storing separate state inside the child component seems to violate the single source of truth for the data, which is partly the reason for using a store pattern in the first place.
Edit:
My question is pertaining to the architecture of the store pattern, rather than asking for a solution to this specific example. I appreciate that the API response in this example does not provide enough information to filter the global store of people, i.e. using a getter, for use in the child component.
What I am asking is: where is an appropriate place to store this second set of people if I wanted to stay true to a store focused design pattern?
It seems wrong somehow to create another store variable to hold the data just for the child component, yet it also seems counter-intuitive to store the second set of data in the child component's state, as that would not be in line with a store pattern approach and keeping components "dumb".
If there were numerous places that required variations on the people data that could only be created by a separate API call, there would either be a) lots of store variables for each "variation" of the data, or b) separate API calls and state in each of these components.
Thanks to tao I've found what I'm looking for:
The best approach would be to return the isOffSick property in the API response, then filtering the single list of people (e.g. using a store getter), thus having a single source of truth for all people in the store and preventing the need for another API request.
If that was not possible, it would make sense to add a secondary store variable for isOffSick people, to be consumed by the child component.

Vue Array converted to Proxy object

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)
}

Vue using v-for to render computed properties after loaded

I'm using v-for to iterate over a computed property, and that computed property depends on a data attribute, which is initiated as null. I will load it in beforeMount.
here is the pseudo-code:
<th v-for="item in computed_list">
{{ item.name }}
</th>
<script>
export default {
name: 'test',
data () {
return {
whole_list: null
}
},
beforeMount () {
this.load()
},
computed: {
computed_list: function() {
if (!this.series) return []
return this.whole_list.slice(1,3)
}
},
methods: {
async load () {
let res = await some_api_call()
this.whole_list = res['data']
}
}
}
</script>
But somehow it failed to render the list, and report TypeError: Cannot read property 'name' of null.
I'm new to Vue and not very familiar with its lifecycle. The basic idea is to render list of data, but those data are loaded somehow after the Vue instance is created. Not sure if it's the correct way to do this.
Initializing a data item to null breaks the VueJS state watching functionality so it won't know about changes to it. Initialize it as an empty object or array instead.
https://012.vuejs.org/guide/best-practices.html
The reason for this is that Vue observes data changes by recursively walking the data object and converting existing properties into reactive getters and setters using Object.defineProperty. If a property is not present when the instance is created, Vue will not be able to track it.
You don’t have to set every single nested property in your data though. It is ok to initialize a field as an empty object, and set it to a new object with nested structures later, because Vue will be able to walk the nested properties of this new object and observe them.

React detect variable change across many files

I have a global variable on my reducer (Redux) code which is a array of objects that contains the data.
I'm constantly passing that variable across multiple files (view files) using connect from React Redux like this:
function mapStateToProps(state) {
const { appointments } = state.Appointment;
return {
appointments
};
}
export default connect(mapStateToProps, {
/* Some methods */
})(Appointment);
When I want to modify a specific element of that array, I pass the reference of that element so I later won't have to deal with replacements. The problem is that someone (a view) is modifying that reference and it's changing it from an Object to a Number type. Is their a easy way such a PropTypes (in the reducer file) to detect who is changing the reference of that array position from an Object to a Number?
Thanks

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