why does the value of an imported constant change (Vue) - javascript

I'm importing an array called CHART_CARDS in a Vue component. This is meant to provide the initial state for another array, called chartCards, which a user can change.
import { CHART_CARDS } from '~/constants/chartCards'
...
export default {
data(){
return {
chartCards: []
}
},
async created(){
if (this.$auth.user.settings && this.$auth.user.settings.length) {
this.chartCards = this.$auth.user.settings
} else {
this.chartCards = CHART_CARDS
}
}
}
So the data property chartCards is either taken from the imported variable or from a pre-existing database table.
Here's where things get weird: I have a method called reset, which is supposed to restore the chartCards variable to the value of the imported array:
async reset () {
console.log('going to reset. CHART_CARDS looks like:')
console.log(CHART_CARDS)
this.chartCards = CHART_CARDS
await this.updateCards()
console.log('chart cards after updating:')
console.log(this.chartCards)
}
Somehow, CHART_CARDS is also changed when chartCards is updated. The two console logs above print the same array, so the reset doesn't work. CHART_CARDS is changed nowhere else in the code; all references to CHART_CARDS are shown in the above code. How is its value being updated?

As others have mentioned in the comments, your CHART_CARDS array probably contains objects, and you are most likely changing these objects somewhere in your code.
There is an easy way around this with some minor API tweaks.
~/constants/chartCards.js
export function getChartCards () {
return [
{...},
{...},
...
]
}
App.vue
import { getChartCards } from '~/constants/chartCards'
...
export default {
data(){
return {
chartCards: []
}
},
async created(){
if (this.$auth.user.settings && this.$auth.user.settings.length) {
this.chartCards = this.$auth.user.settings
} else {
this.chartCards = getChartCards()
}
}
}
Since we're always creating a new array with different objects, changes made to one chartCards instance will not reflect in another.
If you absolutely want to stick with your current API, then that can potentially be achieved as well. You just need to create a deep-copy of your CHART_CARDS object before assigning it.
import { CHART_CARDS } from '~/constants/chartCards'
...
export default {
data(){
return {
chartCards: []
}
},
async created(){
if (this.$auth.user.settings && this.$auth.user.settings.length) {
this.chartCards = this.$auth.user.settings
} else {
this.chartCards = JSON.parse(JSON.stringify(CHART_CARDS)) // This will not work if your CHART_CARDS has methods
}
}
}

Related

Nested namespaces in i18n

My application is rather large, so to have a more organized translation file I want to use nasted namespaces. Example:
{
"contract": {
"index": {
"pageTitle": "Contract"
}
}
The problem with this is when I'm accessing it. With the help of this question I found out I can access the keys inside index by using it as below:
const { t, i18n } = useTranslation('contract', { useSuspense: false });
...
t('index.pageTitle')
The problem is It seems rather unecessary to prefix index. to every key I want to access. What I would like to do is import the namespace index instead of contract, and use it as below:
const { t, i18n } = useTranslation('contract:index', { useSuspense: false });
...
t('pageTitle')
Which doesn't work. I tried contract.index as well. In the official documentation I found nothing about nesting. Is it possible to accomplish what I'm trying to do or will I have to stick with prexifing every key?
Nested namespaces are not supported.
You can decorate the useTranslation hook to provide this extended functionality for pages in the namespace.
import { useTranslation as useTranslationBase } from "react-i18next";
const useTranslation = (ns, page, props={}) => {
const trans = useTranslationBase(ns, props);
return {
...trans,
t: (keys, options) => {
let _keys = keys;
if (!Array.isArray(keys)) _keys = [String(keys)];
_keys = _keys.map(key =>`${page}.${key}`)
return trans.t(_keys, options)
}
}
}
Usage
export default function () {
const { t } = useTranslation('contract', 'index');
return <div>{t(["pageTitle"])}-{t("pageTitle")}</div>
}

Filter array of a computed property using a method in Vue.js

I hope this is not a stupid question. I have a computed property that lists ALL courses. When the user clicks a button calling a method called courseFilters() I would like to filter the computed property to only show Courses that are not archived.
Here is my computed property:
filterCourses() {
const getUser = this.$store.getters['UserData/getUser']
return this.courses.filter((item) => {
if(this.checkAuthorization(['leader'])) {
return item.createdBy === getUser.uid
} else {
return item
}
})
}
Here is my Method:
courseFilters(which) {
if(which == 'hide-archived') {
this.filterCourses.filter((item) => {
if(!item.archive) {
return item
}
})
}
if(which == 'clear') {
this.getCourses(this.$store.getters['AppData/cid'])
}
}
Currently when I click the button nothing changes to the computed property.
I don't think I fully understand the details of your problem, but here's a sketch for a solution that may inspire you:
export default {
data() {
return { areArchivedCoursesVisible: false };
},
computed: {
authorizedCourses() {
const getUser = this.$store.getters['UserData/getUser'];
// The callback in a filter should return true or false.
// I'm not sure if this is exactly what you want.
// If your original code works, then skip this.
return this.courses.filter(
(c) => this.checkAuthorization(['leader']) && c.createdBy === getUser.uid
);
},
visibleCourses() {
// This is probably what you display in your template.
// It's either all of the authorized courses, or the authorized
// courses which are not archived.
return this.areArchivedCoursesVisible
? this.authorizedCourses
: this.this.authorizedCourses.filter((c) => !c.archive);
},
},
methods: {
toggleVisible() {
// Toggle between showing and not showing the archived courses
this.areArchivedCoursesVisible = !this.areArchivedCoursesVisible;
},
},
};
This just holds some state indicating if the archived courses should be shown (toggled via a method). Then you can combine your computed properties to get the correct answer based on the state. In this example, visibleCourses uses the output of the computed property authorizedCourses + the current state.
Also note that I named the computed properties as nouns and not verbs, which I find makes the code much easier to understand.

My mutations aren't working! How do I correct this?

I am trying to set the breakfastMenu array in state as shown below but I can't see the array being filled in my vue-devtools.
I have properly set-up the Vuex methods and checked twice, also I didn't receive any sort of error. So, why do I have a logical error in my code?
store.js:
export default new Vuex.Store({
state: {
menu: [],
breakfastMenu: [],
lunchMenu: [],
dinnerMenu: []
},
mutations: {
'SET_MENU': (state, menuMaster) => {
state.menu = menuMaster;
},
'SET_BREAKFAST_MENU': (state, order) => {
state.breakfastMenu.unshift(order);
},
'SET_LUNCH_MENU': (state, order) => {
state.breakfastMenu.unshift(order);
},
'SET_DINNER_MENU': (state, order) => {
state.breakfastMenu.unshift(order);
},
},
actions: {
initMenu: ({ commit }, menuMaster) => {
commit('SET_MENU', menuMaster)
},
initBreakfastMenu: ({ commit, state }) => {
state.menu.forEach((element) => {
if (element.categoryId == 1) {
commit('SET_BREAKFAST_MENU', element)
}
});
},
initLunchMenu: ({ commit, state }) => {
state.menu.forEach((element) => {
if (element.categoryId == 2) {
commit('SET_LUNCH_MENU', element)
}
});
},
initDinnerMenu: ({ commit, state }) => {
state.menu.forEach((element) => {
if (element.categoryId == 3) {
commit('SET_DINNER_MENU', element)
}
});
},
},
getters: {
getBreakfastMenu(state) {
return state.breakfastMenu
},
getLunchMenu(state) {
return state.lunchMenu
},
getDinnerMenu(state) {
return state.dinnerMenu
},
}
})
Breakfast.vue:
import { mapActions, mapGetters } from 'vuex';
export default {
data() {
return {
breakfastArray: []
};
},
methods: {
...mapActions(['initBreakfastMenu']),
...mapGetters(['getBreakfastMenu']),
},
created() {
this.initBreakfastMenu;
this.breakfastArray = this.getBreakfastMenu;
}
};
No error messages so far!
I need the breakfastMenu array filled in store.js.
Please help out!
A few thoughts.
Firstly, this line:
this.initBreakfastMenu;
You aren't actually calling the method. It should be:
this.initBreakfastMenu();
Next problem is this:
...mapGetters(['getBreakfastMenu']),
The line itself is fine but it's inside your methods. It should be in the computed section.
You haven't included any sample data for state.menu but it's also worth noting that initBreakfastMenu won't do anything unless there is suitable data inside state.menu. I suggest adding some console logging to ensure that everything is working as expected there.
SET_BREAKFAST_MENU, SET_LUNCH_MENU and SET_DINNER_MENU are all modifying state.breakfastMenu. I would assume that this is incorrect and each should be modifying their respective menu.
I would also note that using local data for breakfastArray is suspicious. Generally you'd just want to use the store state directly via the computed property rather than referencing it via local data. This is not necessarily wrong, you may want to detach the component data from the store in this way, but keep in mind that both are referencing the same array so modification to one will also affect the other. You aren't taking a copy of the array, you're just creating a local reference to it.
You should also consider whether you actually need the 4 menu types within your state. If breakfastMenu, lunchMenu and dinnerMenu are all just derived from menu then you'd be better off just implementing those using getters. getters are the store equivalent of computed properties and can contain the relevant filtering logic to generate their value from state.menu.
initBreakfastMenu is an action and you may want to use this.initBreakfastMenu()

comparing array with an observable MobX observable array

I have an observable array in my stores/index.js like this:
class Store {
#observable order = [];
fetchData = flow(function*() {
try {
const myData = yield backendService.retrieveData();
if (myData.length > 0) {
this.order = myData.map(element => {
return element.id;
});
}
} catch (error) { // error handling }
});
...
}
I use this observable array in a component to compare it with another array, if they are not the same, I'll update the observable array through an action.
export default
#inject('store')
#observer
class myComponent extends React.Component {
...
_closeModal() {
const newOrder = this.state.data.map(element => {
return element.id;
});
if (toJS(store.order) !== newOrder) {
store.updateOrder(newOrder);
}
}
}
However the result of the comparison of the two arrays is wrong, even if I don't make any changes, toJS(store.order) !== newOrder still returns true. I don't know what I'm doing wrong but when I console log the two arrays, they return exactly the same content.
In Javascript, two arrays are strict equal, when both reference the same array. So [] === [] returns false! because they are to different arrays.
You can change your code in this way:
if (JSON.stringify(toJS(store.order)) !== JSON.stringify(newOrder))

Trouble triggering reactivity on changed Vuex objects

I'm modifying the value of an existing property on an object that is in an array of objects in my Vuex.store. When I update the store, it is not triggering a re-render of my computed property that is accessing the store. If I reset the stored value to an empty array, and then set it again to my new array, it'll trigger the change. But simply updating the property of the array of objects does not trigger a change.
I have tried using Vue.set() like the docs talk about, and that updates the store, but still does not trigger a re-render of the computed property. What am I missing? Using Vue 2.2.4 and Vuex 2.2.0.
//DEBUG: An example of the updated post I'm adding
let myNewScheduledPost = {
id: 1,
name: 'James'
};
this.$store.dispatch('addScheduledPost', post);
//DEBUG: My store
const options = {
state: {
scheduledPosts: [
{ id: 1, name: 'Jimmy'}
],
},
mutations: {
scheduledPosts: (state, scheduledPosts) => {
//This triggers the reactivity/change so my computed property re-renders
//But of course seems the wrong way to do it.
state.scheduledPosts = [];
state.scheduledPosts = scheduledPosts;
//Neither of these two lines triggers my computed property to re-render, even though there is a change in scheduledPosts
state.scheduledPosts = scheduledPosts;
Vue.set(state, 'scheduledPosts', scheduledPosts);
},
},
actions: {
addScheduledPost({ commit, getters }, newScheduledPost) {
let scheduledPosts = getters.scheduledPosts;
const idx = scheduledPosts.findIndex(existingScheduledPost => existingScheduledPost.id === newScheduledPost.id);
//If the post is already in our list, update that post
if (idx > -1) {
scheduledPosts[idx] = newScheduledPost;
} else {
//Otherwise, create a new one
scheduledPosts.push(newScheduledPost);
}
commit('scheduledPosts', scheduledPosts);
//DEBUG: This DOES have the correct updated change - but my component does not see the change/reactivity.
console.log(getters.scheduledPosts);
}
},
getters: {
scheduledPosts: (state) => {
return state.scheduledPosts;
}
}
};
//DEBUG: Inside of my component
computed: {
mySortedPosts()
{
console.log('im being re-rendered!');
return this.$store.getters.scheduledPosts.sort(function() {
//my sorted function
});
}
}
Your problem is if you are wanting to access a portion of the state you don't use a getter https://vuex.vuejs.org/en/state.html.
computed: {
mySortedPosts(){
return this.$store.state.scheduledPosts
}
}
Getters are for computed properties in the store https://vuex.vuejs.org/en/getters.html. So in your case you might create a getter to sort your scheduled posts then name it sortedScheduledPosts and then you can add it to your components computed properties like you are now.
The key thing is your getter needs to have a different name then your state property just like you would in a component.

Categories