Inject in Pinia throws undifined - vue.js

I'm trying to impelement vue-gates from https://github.com/thonymg/vue-zo
Inject from vue is not giving the instance and throws undefined but when I inject it right in App.vue it works fine
Wondering if someone knows why this happen
import { inject } from 'vue'
import { defineStore } from "pinia";
export const useGatesStore = defineStore("api", () => {
const $zo = inject("$zo");
const setRoles = () => {
console.log($zo)
}
return {
setRoles
};
})
Main.js
import './assets/base.scss'
import 'bootstrap/dist/css/bootstrap.css'
import 'nprogress/nprogress.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import { BootstrapVue } from 'bootstrap-vue'
import { FontAwesomeIcon } from "#fortawesome/vue-fontawesome";
import { createPinia } from 'pinia'
import { createApp, markRaw } from 'vue'
import { router } from './router'
import { VueClipboard } from '#soerenmartius/vue3-clipboard'
import { VueZo } from 'vue-zo'
import Modal from "vue-bs-modal";
import progressBar from './includes/progress-bar';
import App from './App.vue'
import i18n from './includes/i18n'
import 'notyf/notyf.min.css';
import { useAuthStore } from '#/stores';
const pinia = createPinia();
pinia.use(({ store }) => { store.router = markRaw(router) });
const app = createApp(App)
app.config.errorHandler = () => null;
app.config.warnHandler = () => null;
app.use(pinia)
const authStore = useAuthStore();
authStore.attempt(localStorage.getItem('token')).then(() =>
{
app.use(router)
.use(i18n)
.use(BootstrapVue)
.use(Modal)
.use(VueClipboard)
.use(VueZo)
.component("font-awesome-icon", FontAwesomeIcon)
.mount('#app')
progressBar(router)
})

Related

Vue3 and Pinia: store state is undefined in other stores

I want to get users in threadStore from userStore, but users is undefined. And I have no idea why.
Also in userStore threads from threadStore has a correct value.
Maybe that because threadStore installed first than userStore?
But how I can get users from userStore in threadStore?
log image
thread.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { useUserStore } from './user';
export const useThreadStore = defineStore('thread', () => {
const userStore = useUserStore();
console.log(userStore.users); // is undefined
const threads = ref([]);
return {
threads,
}
user.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { useThreadStore } from './thread';
export const useUserStore = defineStore('user', () => {
const threadStore = useThreadStore();
console.log(threadStore.threads); // has a correct value
const users = ref([]);
return {
users,
};
});

How to use Pinia with defineCustomElement in vue 3

is it possible to use the pinia store in a component as an element?
try doing it this way but i get the following error in developer console
index.8ec3cfca.js:1 TypeError: Cannot read properties of undefined (reading '_s')
pinia store
import { UsuarioInternet } from "../../models/UsuariosInternet.model"
import { defineStore } from "pinia";
export const useFAUsuarioInternet = defineStore("useFAUsuarioInternet",{
state:() => ({
items: <UsuarioInternet[]>([])
}),
getters:{
listaUsuario(state){
return state.items;
}
},
actions:{
createNewUser(item :UsuarioInternet){
if(!item) return;
this.items.push(item);
},
findIndexById(id: number){
return this.items.findIndex((item) => item.id == id);
}
}
})
component
<template>
<p>
setsetsetestset
</p>
</template>
<script lang="ts" setup >
import { ref , onMounted, computed} from 'vue';
import { useFAUsuarioInternet } from "../stores/general/useFAUsuariosInternet";
import { UsuarioInternet } from "../models/UsuariosInternet.model";
let Usuario = ref<UsuarioInternet>(new UsuarioInternet);
//mounted
onMounted(() => {
});
const mainStore = useFAUsuarioInternet();
//call action
const saveUser = () => {
mainStore.createNewUser(Usuario.value);
}
//getters
const lista = computed(() => mainStore.listaUsuario)
</script>
Main.ts
import { createApp, defineCustomElement } from 'vue'
import UsuariosInternet from './pages/general/UsuariosInternet.ce.vue'
import homeTest from './components/homeTest.ce.vue'
const element = defineCustomElement(UsuariosInternet);
customElements.define("usuarios-internet", element);
const element2 = defineCustomElement(homeTest);
customElements.define('home-test', element2);
Here's the main.ts you want:
import { defineCustomElement } from "vue";
import { createPinia, setActivePinia } from "pinia";
import UsuariosInternet from "./pages/general/UsuariosInternet.ce.vue";
import homeTest from "./components/homeTest.ce.vue";
// Here you create a standalone pinia
setActivePinia(createPinia());
const element = defineCustomElement(UsuariosInternet);
customElements.define("usuarios-internet", element);
const element2 = defineCustomElement(homeTest);
customElements.define("home-test", element2);
This will create a global store for all your components.
See: https://github.com/vuejs/pinia/discussions/1085#discussioncomment-2219587

Compile error when trying to access a getter function in Veux 4 store of Quasar V2 beta app

Compile error when trying to access a getter function in Veux 4 store of Quasar V2 beta app.
Unsafe member access ['item/getRandomNumber'] on an any value.eslint#typescript-eslint/no-unsafe-member-access
Error not going even after disabling it.
Please explain the cause the error and how to sort it out.
setup() {
return { randomNumber: computed(() => store.getters['item/getRandomNumber']) }
},
getter.ts under module item
import { GetterTree } from 'vuex';
import { StateInterface } from '../index';
import { ItemStateInterface } from './state';
const getters: GetterTree<ItemStateInterface, StateInterface> = {
getRandomNumber ( /* context */) {
return 20;
}
};
export default getters;
index.ts under module item
import { Module } from 'vuex';
import { StateInterface } from '../index';
import state, { ItemStateInterface } from './state';
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
const itemModule: Module<ItemStateInterface, StateInterface> = {
namespaced: true,
actions,
getters,
mutations,
state
};
export default itemModule;
store/index.ts
import { store } from 'quasar/wrappers'
import { InjectionKey } from 'vue'
import {
createStore,
Store as VuexStore,
useStore as vuexUseStore,
} from 'vuex'
import item from './module-item'
import { ItemStateInterface } from './module-item/state';
export interface StateInterface {
item : ItemStateInterface
}
declare module '#vue/runtime-core' {
interface ComponentCustomProperties {
$store: VuexStore<StateInterface>
}
}
export const storeKey: InjectionKey<VuexStore<StateInterface>> = Symbol('vuex-key')
export default store(function (/* { ssrContext } */) {
const Store = createStore<StateInterface>({
modules: {
item
},
// enable strict mode (adds overhead!)
// for dev mode and --debug builds only
strict: !!process.env.DEBUGGING
})
return Store;
})
export function useStore() {
return vuexUseStore(storeKey)
}

How to use Vuex mapGetters with Vue 3 SFC Script Setup syntax?

I'm refactoring component from regular Vue 3 Composition API to Script Setup syntax. Starting point:
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { mapGetters } from 'vuex';
export default defineComponent({
name: 'MyCoolBareComponent',
computed: {
...mapGetters('auth', ['isAdmin']),
},
});
</script>
Current Vue v3 migration documentation, SFC Composition API Syntax Sugar (< script setup >), links to this RFC page: https://github.com/vuejs/rfcs/pull/182
There is only one example for using computed reactive property:
export const computedMsg = computed(() => props.msg + '!!!')
As there is no current Vuex 4 documentation available that is mentioning <scrip setup>, it remains unclear to me how I should be using mapGetters when using this syntax? Or what is the correct way of going about this with Vuex 4?
tldr: scroll down to final result
There is now better documentation and the simple answer is: You don't need mapGetters but you can implement it yourself.
https://next.vuex.vuejs.org/guide/composition-api.html#accessing-state-and-getters
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const count = computed(() => store.getters.count)
</script>
If you have many getters you want to turn into a "computed property" you could use something as "intuitive" as this:
const { countIsOdd, countIsEven } = Object.fromEntries(Object.keys(store.getters).map(getter => [getter, computed(() => store.getters[getter])]))
Put that into a function and it even looks nice.
const mapGetters = (getters) => {
return Object.fromEntries(Object.keys(getters).map(getter => [getter, computed(() => getters[getter])]))
}
const { countIsOdd, countIsEven } = mapGetters(store.getters)
Put that function into a file and export it as a module...
// lib.js
import { computed } from 'vue'
import { useStore } from 'vuex'
const mapGetters = () => {
const store = useStore()
return Object.fromEntries(Object.keys(store.getters).map(getter => [getter, computed(() => store.getters[getter])]))
}
export { mapGetters }
...and you can easily use it in all your components.
// components/MyComponent.vue
<script setup>
import { mapGetters } from '../lib'
const { countIsOdd, countIsEven } = mapGetters()
</script>
Final result:
Here's the final lib.js I came up with:
import { computed } from 'vue'
import { useStore } from 'vuex'
const mapState = () => {
const store = useStore()
return Object.fromEntries(
Object.keys(store.state).map(
key => [key, computed(() => store.state[key])]
)
)
}
const mapGetters = () => {
const store = useStore()
return Object.fromEntries(
Object.keys(store.getters).map(
getter => [getter, computed(() => store.getters[getter])]
)
)
}
const mapMutations = () => {
const store = useStore()
return Object.fromEntries(
Object.keys(store._mutations).map(
mutation => [mutation, value => store.commit(mutation, value)]
)
)
}
const mapActions = () => {
const store = useStore()
return Object.fromEntries(
Object.keys(store._actions).map(
action => [action, value => store.dispatch(action, value)]
)
)
}
export { mapState, mapGetters, mapMutations, mapActions }
Using this in the component looks like this:
<template>
Count: {{ count }}
Odd: {{ counterIsOdd }}
Even: {{ counterIsEven }}
<button #click="countUp">count up</button>
<button #click="countDown">count down</button>
<button #click="getRemoteCount('https://api.countapi.xyz')">
get remote count
</button>
</template>
<script setup>
import { mapState, mapGetters, mapMutations, mapActions } from '../lib'
// computed properties
const { count } = mapState()
const { countIsOdd, countIsEvent } = mapGetters()
// commit/dispatch functions
const { countUp, countDown } = mapMutations()
const { getRemoteCount } = mapActions()
</script>
Any feedback on this would be very appreciated.
So far this syntax seems to be working. However, I'm hoping that Vuex would develop a cleaner way for exposing computed getters for template.
If you know a better way, we'd love to hear!
<script setup lang="ts">
import { mapGetters } from 'vuex';
export const name = 'MyCoolBareComponent';
export default {
computed: {
...mapGetters('user', ['profile', 'roles']),
},
};
</script>
import {useStore} from "vuex";
import {computed} from "vue";
const {getEvents, getSelectedTag} = useStore().getters;
const events = computed(() => getEvents)
const selectedTag = computed(() => getSelectedTag)
i do this and for me is working
You don't need to export anything, an SFC will register all variables and components for you and make them available in template.
An SFC automatically infers the component's name from its filename.
Here are a few examples that may be useful:
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
import MyComponent from './components/MyComponent'
const store = useStore()
const data = 'Random string as a data'
// without module/data
const myAction = () => store.dispatch('myAction')
// with data
const mySecondAction = () => store.dispatch('mySecondAction', data)
// with module
const myMutation = () => store.commit('moduleName/myMutation')
// with module/data
const myNewMutation = () => store.commit('moduleName/myNewMutation', data)
const myStateVariable = computed(() => store.state.myStateVariable)
// with module
const myGetter = computed(() => store.getters.moduleName.myGetter)
// replace using of mapState/mapGetters
const state = computed(() => store.state)
// and then
console.log(state.myStateVariable)
console.log(state.mySecondStateVariable)
....
</script>
You can do something like this
import { mapGetters } from "vuex"
setup() {
return {
...mapGetters("myModule", ["doSomething"])
}
}
Follow this:
https://vuex.vuejs.org/guide/typescript-support.html#typing-usestore-composition-function
Here is an example:
store.ts
import { InjectionKey } from 'vue'
import { createStore, Store } from 'vuex'
// define your typings for the store state
export interface State {
token: string|null
}
// define injection key
export const key: InjectionKey<Store<State>> = Symbol()
export const store = createStore<State>({
state: {
token: localStorage.getItem('token') ? localStorage.getItem('token'):'',
}
})
main.js
import { store, key } from './store'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// pass the injection key
app
.use(store, key)
.mount('#app')
In a vue component
<script setup>
import { onMounted } from 'vue'
import { useStore } from 'vuex'
import { key } from './store'
const token = useStore(key)
onMounted(() => {
console.log(store.state.token)
})
</script>

Import bootstrap error: Unexpected token, test-utils, working in one test but not another

I'm new to unit testing with jest in vue.
I have added the plugin unit test from here: https://www.npmjs.com/package/#vue/cli-plugin-unit-jest
It have created a test folder with another folder called unit where I have placed 2 files: Footer.spec.js and Main.spec.js. It also created a jest.config.js file.
Footer.spec.js
import '../../src/plugins/fontawesome'
import { mount } from '#vue/test-utils'
import Footer from '../../src/components/Footer/Footer.vue'
import BootstrapVue from 'bootstrap-vue'
import { createLocalVue } from '#vue/test-utils'
describe('Footer', () => {
const localVue = createLocalVue()
localVue.use(BootstrapVue)
const wrapper = mount(Footer, {localVue})
it('has a FooterPhoneNumber', () => {
expect(wrapper.html()).toContain('some HTML')
})
it('has a FooterEmail', () => {
expect(wrapper.html()).toContain('some HTML')
})
})
Main.spec.js
import '../../src/plugins/fontawesome'
import { mount } from '#vue/test-utils'
import Main from '../../src/components/Main.vue'
import BootstrapVueMain from 'bootstrap-vue'
import { createLocalVueMain } from '#vue/test-utils'
describe('Main', () => {
const localVueMain = createLocalVueMain()
localVueMain.use(BootstrapVueMain)
const wrapper = mount(Main, {localVueMain})
it('the modal for browser not compatible', () => {
expect(wrapper.html()).toContain('some HTML')
})
})
From the jest.config.js
transformIgnorePatterns: [
'/node_modules/(?!(bootstrap|bootstrap-vue)/)',
'/node_modules/',
],
I'm getting an error because of 2 imports:
import 'bootstrap/dist/css/bootstrap.css'
| ^
18 | import 'bootstrap-vue/dist/bootstrap-vue.css'
The Footer.spec.js pass the test but the Main.spec.js dosen't.
Any suggestions on how to fix it?
In the Main.spec.js you are importing and using createLocalVueMain(), instead of createLocalVue(). Because of that, BootstrapVue is not registered to localVue instance - because it was not created.