In my store module /store/template.js I have:
const templateConfig = {
branding: {
button: {
secondary: {
background_color: '#603314',
background_image: ''
}
}
}
}
export const state = () => ({
branding: {},
...
})
export const actions = {
initializeStore (state) {
state.branding = templateConfig.branding
}
}
(initializeStore() is called when app initially loads)
I want to retrieve the branding the branding object in my component:
computed: {
...mapState({
branding: state => state.template.branding
})
}
But when trying to console.log() branding I see this:
Why don't I simply see the branding object? (and what on earth is this?)
You need to always use a mutation to change state. You can call one from your action:
export const mutations = {
SET_BRANDING(state, payload) {
state.branding = payload;
}
}
export const actions = {
initializeStore ({ commit }) {
commit('SET_BRANDING', templateConfig.branding);
}
}
What you're seeing with the observer is normal, and indicates that the branding object has been successfully mapped and accessed.
What you see is Vue's observable object, which is how Vue implements reactivity. Without this, there would be no reactivity, and you will see such a wrapper on all top-level reactive objects. You can pretend it's not there.
Vue in fact applies this same "wrapper" to the data object internally to make it observable:
Internally, Vue uses this on the object returned by the data function.
You won't see it on other reactive properties, but if they're reactive, they belong to some parent observable object.
You need to import { mapState, mapActions } from 'vuex' (already done I guess).
And then, you can write this
...mapState(['branding']) // or ...mapState('#namespacedModule', ['branding'])
Still, why do you not simply put the state directly (with your background_color) rather than going through a Vuex action ?
If you want to keep it this way, do not forget to await this.initializeStore() in your component before trying to access the state.
Related
I am working on my first Vue project. I'm used to React and vanilla js, but just getting my head around a few concepts in Vue here.
In particular, importing state and action props from a Pinia store, and seemingly having to import those multiple times in a single Vue component (something I don't need to do in React).
In this example, I am importing a simple count value, and an increment function, and trying to use these in a few different places:
<script setup>
// I import everything initially in setup, which works fine,
// and these props (currentCount and incrementCount)
// can be used in my template:
import { storeToRefs } from 'pinia';
import { useStore } from '#/stores/store';
const { currentCount } = storeToRefs(useStore());
const { incrementCount } = useStore();
</script>
<template>
<main>
Current count: {{ currentCount }}
<button #click="incrementCount">Increment</button>
</main>
</template>
<script>
// I can't use store values from setup here.
// This doesn't work:
// console.log(currentCount);
// I also can't import store values here.
// I get the following error:
// "getActivePinia was called with no active Pinia"
// const { currentCount } = storeToRefs(useStore());
export default {
mounted() {
// I have to import store values here for them to work:
const { currentCount } = storeToRefs(useStore());
console.log(currentCount);
},
watch: {
// weirdly, this reference to watching "currentCount" works:
currentCount() {
// I also have to import store values here for them to work:
const { currentCount } = storeToRefs(useStore());
console.log(currentCount);
},
},
};
</script>
As you can see, if I want to use store values in my template, on mount, and in a watcher (whereby I'd use React's useEffect hook) I am having to import the store props 3 times in total.
Is this correct / normal? Is there a simpler way to achieve what I'm doing, where I only import props once? I want to be sure I haven't missed something and am not doing something in an unusual way.
Thanks for any help and advice!
Pinia was designed with Composition API in mind.
So its intended usage is inside setup() function, where you'd only import it once.
To use it outside of a setup() function, you have two main routes:
inside components, you can just return it from setup() and it becomes available in any hook/method/getter. Either as this.store or spread:
import { useStore } from '#/store'
import { toRefs } from 'vue'
// or from '#vue/composition-api' in Vue2
export default {
setup: () => ({ ...toRefs(useStore()) })
}
/* this makes every state prop, getter or action directly available
on current component instance. In your case, `this.currentCount`.
Obviously, you can also make the entire store available as `this.someStore`:
setup: () => ({ someStore: useSomeStore() })
// now you can use `this.someStore` anywhere
*/
a more general approach is to export the pinia instance (returned by createPinia()), from main.(js|ts), import it where you need the store and then call useStore() passing the pinia instance as an argument.
This can be done anywhere, even outside of components.
Generic example:
import { pinia } from 'main.js'
import { useSomeStore } from '#/store'
const someStore = useSomeStore(pinia);
I should probably also mention the mapState helper provided by pinia. It allows you to select only a few of the keys exposed to current instance. Example:
import { mapState } from 'pinia'
// ...
computed: {
...mapState(useSomeStore, [ 'currentCount'])
}
// Now `this.currentCount` is available
Note: mapState is weirdly named, as it allows you to access more than just state props (also getters and actions). It was named mapState to match the similar helper from vuex.
An even more general approach is to add your store as global, using the plugin registration API in Vue2:
import { useSomeStore } from '#/store';
import { createPinia } from 'pinia';
const pinia = createPinia();
const someStorePlugin = {
install(Vue, options) {
Vue.prototype.someStore = useSomeStore(options.pinia)
}
};
Vue.use(someStorePlugin, { pinia });
new Vue({ pinia });
After this, every single component of your Vue instance will have this.someStore available on it, without you needing to import it.
Note: I haven't tested adding a store in globals (and I definitely advise against it - you should avoid globals), but i expect it to work.
If you want to combine pinia stores with the options API, one way to do it is to use the setup() function inside the options to call useStore:
<script>
import { useStore } from '#/stores/store';
export default {
setup() {
const store = useStore();
return {store}
},
watch: {
store.currentBrightness(newVal, oldVal){
// your code
}
},
methods: {
// inside methods use this.store
},
mounted() {
console.log(this.store.currentCount);
}
}
</script>
Some might consider this as a unwanted mix of composition and options API, but in my view it is a quite good solution for pinia stores.
Nechoj, has the most straightforward answer. Also if you have multiple stores you can always import the stores as necessary into a parent component then use inject just add some parts. For example I have a route data that is called via an api, I don't need it everywhere all the time so i call it in a parent then use inject to use those routes in a drop down that might be a great grandchild component. I don't need that whole utils store just the routes.
index page:
import { useUtilsStore } from "src/stores/utilsStore";
const passengerRoutes = computed(() => utilsStore.getPassengerRoutes);
provide("passengerRoutes", passengerRoutes);
grandchild component:
const compRoutes = inject("passengerRoutes");
Im trying to add a simple common action in vuex that returns a bool,but it looks like it doesn't return what I need.
My child component call:
<ArrowDownIcon
class="campaigns-grid__header-direction"
v-if="isOrderedBy(column.id)"
class="{'campaigns-grid__header-direction--asc': order.isDescending}"
/>
My imports and vuex call on root component:
import { createNamespacedHelpers } from 'vuex'
const { mapActions } = createNamespacedHelpers('campaigns')
export default {
methods: {
...mapActions(['isOrderedBy', 'orderBy'])
}
}
My vuex module ('campaigns'):
export default {
namespaced: true,
actions: {
isOrderedBy (column) {
if (column === 'test') {
return true
}
return false
},
}
}
As much as this might possibly work, You shouldn't use Vuex Actions to return Boolean values since Vuex actions return promises. The ideal process of working with vuex is:
-> dispatch Action -> Action makes Network calls -> Actions Commits Data to State -> Getters Pull data from State
To Solve this issue use Mixins, Since you're trying to make the isOrderedBy(..) function available application wide, and you also want it to return a boolean value depending on the provided argument.
Create a mixin folder if you don't already have one, and add an orderMixin.js file: the file should contain the isOrderedBy() function as a method.
export const orderMixin = {
methods: {
isOrderedBy(column) {
return column === 'test'
}
}
}
Then within your component import the mixin and use it.
<template>
...
<ArrowDownIcon
class="campaigns-grid__header-direction"
v-if="isOrderedBy(column.id)"
class="{'campaigns-grid__header-direction--asc': order.isDescending}"
/>
...
</template>
<script>
import { orderMixin } from 'path-to-mixins/orderMixin'
export default {
mixins: [orderMixin],
...
}
</script>
In vuex action returns promise.
So if you want to get return value from the action you can do like
if (await isOrderedBy()) {
console.log('isOrderBy returns true')
} else {
console.log('isOrderBy returns false')
}
I have a component with data as:
export default {
data() {
return {
allItems: [],
orderItems: {},
itemAdded: [],
};
},
I want to access the itemAdded array data which is present in the local component in the Vuex store.js file.
Is there anyway to do so?
You can't access component data from vuex, what you can do is pass the data as the payload when dispatching a vuex action.
for example, your vuex action:
actions: {
doSomething(context, itemAdded) {
// Do something with itemAdded
}
}
and dispatch the action from your component:
this.$store.dispatch('doSomething', this.itemAdded);
If you want to change itemAdded value on the component from the vuex action, you can return the value like this:
actions: {
doSomething(context, itemAdded) {
// Do something with itemAdded
return itemAdded;
}
}
and on your component:
// Need to use await since dispatch return a promise, you can also use `.then`
this.itemAdded = await this.$store.dispatch('doSomething', this.itemAdded);
Keep in mind that Vuex is a state management, and vuex action should do an action that's related to a state, not a place to store a 'reusable function'. I can't think of any case why would you want to return the edited value from the action, but if you think it makes sense / it's related to state, then you can do it this way.
I am trying to write a simple todoList using vue.js and I want to save those todos into cookies before the vue instance is destroyed. But I find it weird that though I wrote callback in beforeDestory hook, the hook is never called.
I checked Vue documents and could not find any hint.
when I tried to
save those todos into cookies by adding callback to window.onbeforeunload and window.onunload, it works.
my code is like
computed: {
todos() {
return this.$store.getters.todos
},
...
},
beforeDestroy() {
myStorage.setTodos(this.todos)
}
todos is a array defined in store.js, which has been imported in main.js, like
import myStorage from '#/utils/storage'
...
const store = new Vuex.Store({
state: {
todos: myStorage.getTodos()
...
},
getters: {
todos: state => state.todos
}
and myStorage is defined as:
import Cookies from 'js-cookie'
const todoKey = 'todo'
const setTodos = (todos) => {
Cookies.set(todoKey, JSON.stringify(todos))
}
const getTodos = () => {
const todoString = Cookies.get(todoKey)
let result = []
if (todoString) {
const todoParsed = JSON.parse(todoString)
if (todoParsed instanceof Array) {
result = todoParsed
}
}
return result
}
export default {
setTodos: setTodos,
getTodos: getTodos
}
I am using vue 2.6.10, and my project is constructed by vue-cli3.
I develop this todolist using Chrome on Window 10.
I expect that after I close the window or after I refresh the window, the todolist can still fetch todo written previously from cookies. But the fact is that the beforeDestory hook is never called.
When you refresh the window, the component's beforeDestroy() is not called, because you are not programmatically destroying the component, but ending the entire browser session.
A better solution would simply to call myStorage.setTodos whenever the todos object in the component is mutated. You can do that by setting up a watcher for the computed property:
computed: {
todos() {
return this.$store.getters.todos
},
},
watch: {
todos() {
myStorage.setTodos(this.todos)
}
}
Altertively, you let the VueX store handle the storage. It is unclear from your question if you are mutating the todos state: if you are mutating it, you can also do myStorage.setTodos in the store. The actual component can be dumb in that sense, so that all it needs to do is to update the store.
I have a Mixins that get's the data from the sessionStorage, and that data is being used in all component and API call to get data is in the Main component(App.vue) that fetch the data and set's into the sessionStorage.
beforeCreate() {
if (!sessionStorage.getItem('constants')) {
axios.get('/data').then(function(response) {
sessionStorage.setItem('constants',JSON.stringify(response.data.result));
});
},
In the Mixins I'm not getting data from the sessionStorage, Because Mixins runs before the App.vue Component and that does not work.
I also tried to keep the fetch call inside the Mixins but, fetch call is going multiple times even though, I have the condition while getting data from sessionStorag.
import Vue from 'vue';
const Constants = Vue.mixin({
data() {
const constant = JSON.parse(sessionStorage.getItem('constants'));
return {
get constantsData() {
return {
COUNTRY: constant.COUNTRY,
STATE: constant.STATE,
};
},
};
},
});
export default Constants;
What is the best way to utilize Mixins with the API data. ?
You can do
beforeCreate() {
if (!sessionStorage.getItem('constants')) {
axios.get('/data').then(function(response) {
sessionStorage.setItem('constants',JSON.stringify(response.data.result));
this.constant = response.data.result;
});
}
And in mixins:
import Vue from 'vue';
const Constants = Vue.mixin({
data() {
return {
constant: {}
}
},
});
export default Constants;
But normally I prefer using vuex to share data between component.
I found the answer, I used method in the mixins. because I'm using constant as a dropdown value (ie. country name, state from the API). I want to call method only when the dropdown component is created
const Constants = Vue.mixin({
methods: {
getConstantData() {
return JSON.parse(sessionStorage.getItem('constants'));
},
},
data() {
return {};
},
});
export default Constants;
and I can use getConstantData method in any component to access the data just getConstantData()