How to watch localeStorage changes in Pinia state? - vue.js

I have an item called nuxt-color-mode saved in localeStorage. It has either dark or light value. And I would like to watch the value of this item from any component. So I put the Setup & Option Stores that I tried for now! By the way I'm using Nuxt 3!
'~/stores/colorTheme.ts/'
import { defineStore } from "pinia";
// Option Stores
export const useColorThemeStore = defineStore('colorThemeStore', {
state: async () => {
return { mode: await localStorage.getItem('nuxt-color-mode')};
},
getters: {},
actions: {}
});
//Setup Stores
export const useColorThemeStore = defineStore('colorThemeStore', async () => {
const colorMode = await localStorage.getItem('nuxt-color-mode');
const mode = ref(colorMode);
return { mode }
});
'~/component/Portfolio.vue'
<script setup lang="ts">
import { useColorThemeStore } from '~/stores/colorTheme';
const mode = useColorThemeStore(); // Got undedfined!
watch(mode, () => {
console.log('Mode = ', mode) // Doesn't watch or print anything!
})
</script>

Related

Vuex + Jest + Composition API: How to check if an action has been called

I am working on a project built on Vue3 and composition API and writing test cases.
The component I want to test is like below.
Home.vue
<template>
<div>
<Child #onChangeValue="onChangeValue" />
</div>
</template>
<script lang="ts>
...
const onChangeValue = (value: string) => {
store.dispatch("changeValueAction", {
value: value,
});
};
</scirpt>
Now I want to test if changeValueAction has been called.
Home.spec.ts
...
import { key, store } from '#/store';
describe("Test Home component", () => {
const wrapper = mount(Home, {
global: {
plugins: [[store, key]],
},
});
it("Test onChangeValue", () => {
const child = wrapper.findComponent(Child);
child.vm.$emit("onChangeValue", "Hello, world");
// I want to check changeValueAction has been called.
expect(wrapper.vm.store.state.moduleA.value).toBe("Hello, world");
});
});
I can confirm the state has actually been updated successfully in the test case above but I am wondering how I can mock action and check if it has been called.
How can I do it?
I have sort of a similar setup.
I don't want to test the actual store just that the method within the component is calling dispatch with a certain value.
This is what I've done.
favorite.spec.ts
import {key} from '#/store';
let storeMock: any;
beforeEach(async () => {
storeMock = createStore({});
});
test(`Should remove favorite`, async () => {
const wrapper = mount(Component, {
propsData: {
item: mockItemObj
},
global: {
plugins: [[storeMock, key]],
}
});
const spyDispatch = jest.spyOn(storeMock, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
expect(spyDispatch).toHaveBeenCalledWith("favoritesState/deleteFavorite", favoriteId);
});
This is the Component method:
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", favoriteId);
}
return {
removeFavorite
}
}
Hope this will help you further :)

Vue composable scoping issue

We're using Vue 2 with the Vue Composition API and we're trying to create a composable that will expose application preferences:
// useApplicationPreferences.ts
import { ref, watch } from '#vue/composition-api'
import { useSetDarkModeMutation, useViewerQuery } from 'src/graphql/generated/operations'
const darkMode = ref(false) // global scope
export const useApplicationPreferences = () => {
const { mutate: darkModeMutation } = useSetDarkModeMutation(() => ({
variables: {
darkMode: darkMode.value,
},
}))
watch(darkMode, async (newDarkMode) => {
console.log('darkMode: ', newDarkMode)
await darkModeMutation()
})
return { darkMode }
}
This code works fine but when the composable is used in two components that are rendered at the same time we can see that watch has been triggered twice. This is easily solved by moving the watch function to the global scope (outside the function).
However, the issue then is that we can't use the darkModeMutation. This graphql mutation can not be moved to the global scope outside of the function, if we do that the page doesn't even get rendered.
The goal is to have darkMode available in many places and when the value of the darkMode ref changes the mutation is only triggered once. How can this be achieved?
Solved the issue by creating a callable function that starts watch only when required (i.e. only once somewhere in the app).
// useApplicationPreferences.ts
import { ref, watch } from '#vue/composition-api'
import { useSetDarkModeMutation, useViewerQuery } from 'src/graphql/generated/operations'
const darkMode = ref(false) // global scope
export const useApplicationPreferences = () => {
const { mutate: darkModeMutation } = useSetDarkModeMutation(() => ({
variables: {
darkMode: darkMode.value,
},
}))
const startWatch = () => {
watch(darkMode, async (newDarkMode) => {
await darkModeMutation()
})
}
return { darkMode, startWatch }
}
Which the can be called once in MainLayout.vue:
// MainLayout.vue
import { defineComponent } from '#vue/composition-api'
import { useApplicationPreferences } from 'useApplicationPreferences'
export default defineComponent({
setup() {
const { startWatch } = useApplicationPreferences()
startWatch()
},
})
All other components can then simply consume (get/set) the darkMode ref as required while watch is only running once.
// Settings.vue
import { defineComponent } from '#vue/composition-api'
import { useApplicationPreferences } from 'useApplicationPreferences'
export default defineComponent({
setup() {
const { darkMode } = useApplicationPreferences()
return { darkMode }
},
})

in Vuex, How to load state and use data from the state when the application loads/renders first ?, I am using nuxt, Vue and Vuex as store

I am trying to load data from a JSON file into the VueX store, but the state does not get loaded until I try to refresh the VueX Store manually.
what I am trying to achieve is, before the app renders, the state should be loaded with the data.
Like before I access the homepage.
But I see on the Vue Devtools, that if set it to recording mode, then the app loads the data.
Below is code from store/index.js
//store/index.js
const exec = (method, { rootState, dispatch }, app) => {
const dispatches = [];
Object.keys(rootState).forEach(async (s) => {
dispatches.push(await dispatch(`${s}/${method}`, app));
});
return dispatches;
};
export const actions = {
nuxtServerInit(store, ctx) {
console.log('nuxtServerInit');
exec('init', store, ctx);
},
nuxtClientInit(store, ctx) {
console.log('nuxtClientInit');
exec('init', store, ctx);
},
init(store, ctx) {
console.log('nuxtInit');
exec('init', store, ctx);
},
};
store/app.js
//store/app.js
export const state = () => ({
config: {},
});
export const mutations = {
SET_CONFIG(state, config) {
state.config = config;
}
}
};
export const getters = {
config: (state) => state.config,
};
const loadConfig = ({ commit }) => {
const siteConfig = require('../config/data.json');
const appConfig = JSON.parse(JSON.stringify(siteConfig.properties));
commit('SET_CONFIG', appConfig);
};
export const actions = {
init(store, ctx) {
loadConfig(store);
},
};
Here the state is empty when the app loads. How can I access that when the app loads?
I normally call the init action of my store in the layout.
When this is too late you could also do it in a plugin, I guess.
You can use the context.store in the plugin.
// plugins/init.js
export default ({ store }) => {
store.dispatch("init")
}
// store/index.js
export actions = {
init(context) {
// ...
}
}

Using Axios within nuxtServerInit()

I need to get remote data to be displayed in every pages.
This call is perfomed in store/index.js:
export const state = () => ({
contact: {
hello: "World"
}
});
export const actions = {
async nuxtServerInit({ commit, state }) {
const { contactData } = await this.$axios.get("/contact");
commit("SET_CONTACT", contactData);
}
};
export const mutations = {
SET_CONTACT(state, contactData) {
state.contact = contactData;
}
};
Problem is that the value of contact turns to undefined in the store, whereas expected content is retrieved through Axios (the retrieved content is displayed in the SSR console...)
What am I missing here?
export const actions = {
async nuxtServerInit({ commit, state }, {app} ) {
const { contactData } = await app.$axios.get("/contact");
commit("SET_CONTACT", contactData);
}
};

Pre-fetch api data in Vuex with Nuxt.js

I am trying to pre-fetch some data and update Vuex before client-side kicks in.
store/index.js
export const state = () => ({});
export const getters = {};
export const actions = {
async nuxtServerInit ({ dispatch }) {
await dispatch('nasa/getImages');
}
};
store/moduleName.js
import fetch from 'node-fetch';
export const state = () => ({
images: []
});
export const mutations = {
storeImages(state, data) {
state.images = [];
state.images.push(...data);
console.log(state.images[0]); <- this logs in the terminal
}
}
export const actions = {
getImages(store) {
return fetch('api/url').then(response => {
response.json().then(function(data) {
store.commit('storeImages', data.collection.items.slice(0, 24));
});
});
}
}
My mutation gets triggered by nuxtServerInit and I am getting the first element logged in the terminal on page load. My store in the client-side however, is empty.
What am I missing?
With help from a friend we have managed to fix this issue by removing node-fetch and adding axios to Vuex instead.
The only change made was in store/moduleName.js which now looks like:
import Axios from 'axios'
export const state = () => ({
images: []
});
export const mutations = {
storeImages(state, data) {
state.images.push(...data);
}
}
export const actions = {
async getImages(store) {
let res = await Axios.get('api/url');
store.commit('storeImages', res.data.collection.items.slice(0, 24));
}
}