How to use Pinia with defineCustomElement in vue 3 - vue.js

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

Related

Inject in Pinia throws undifined

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

Vuex useStore is giving me undefined in this component

<template>
<div :style="styles">
<Spinner v-if="showSpinner" class="w-14 h-14" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Spinner from '../common/Spinner.vue';
export default defineComponent({
name: 'FullQuestionnaire',
components: { Spinner },
});
</script>
<script setup lang="ts">
import { withDefaults, defineProps, computed, ref, toRefs, watch } from 'vue';
import { useStore } from 'vuex';
const props = withDefaults(
defineProps<{
isLoading?: boolean;
additions: AdditionalParameters;
questionnaireConfig: QuestionnaireConfig;
questionnaireStyling?: RawQuestionnaireStyles | null;
submitAnswers: SubmitAnswersFunction;
goBack: () => void;
}>(),
{ isLoading: undefined, questionnaireStyling: null }
);
const store = useStore();
const redirectUrl = computed(
() => thankYouScreenInfo.value.properties.redirect_url ?? ''
);
const processedRedirectUrl = computed(() => {
const url = populatePlaceholdersInLinkQueryParameters(
redirectUrl.value,
''
// store.getters.userProfile
);
});
</script>
Hi, I am new to Vue and still learning. As you can see in the code I am importing useStore from vuex but when I tried to console.log(store) after this declaration const store = useStore(); its giving me undefined.
Vue Document
When I look into the vuex documentation. the example is using the useStore when setting up. Can I call const store = useStore(); outside of the setup?
Why is that?

How to show return data from vue apollo within a <script setup> syntax

I followed this tutorial in Vue Apollo for retrieving data with fake api https://www.apollographql.com/blog/frontend/getting-started-with-vue-apollo/.
I however have a code where I use <script setup></script> instead of the usual setup() method where everything there is placed.
How should I return data on my elements in this instance?
So this is currently my vue file:
<script setup>
import { storeToRefs } from 'pinia';
import { useAuthStore, useUsersStore } from '#/stores';
import gql from 'graphql-tag'
import { useQuery } from '#vue/apollo-composable'
const authStore = useAuthStore();
const { user: authUser } = storeToRefs(authStore);
const usersStore = useUsersStore();
const { users } = storeToRefs(usersStore);
usersStore.getAll();
apollo: gql`
query Characters {
characters {
results {
id
name
image
}
}
}
`
const { result, loading, error } = useQuery(CHARACTERS_QUERY);
}
</script>
<template>
<div>
<h1>Hi {{authUser?.firstName}}!</h1>
<div v-if="users.loading" class="spinner-border spinner-border-sm"></div>
<div v-if="users.error" class="text-danger">Error loading users: {{users.error}}</div>
<p v-if="error">Something went wrong...</p>
<p v-if="loading">Loading...</p>
<p v-else v-for="character in result.characters.results" :key="character.id">
{{ character.name }}
</p>
<div></div>
</div>
</template>
Then my main.js is:
import { createApp, provide, h } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import { router } from './helpers';
// setup fake backend
import { fakeBackend } from './helpers';
fakeBackend();
import { ApolloClient, InMemoryCache } from '#apollo/client/core'
import { DefaultApolloClient } from '#vue/apollo-composable'
import { createApolloProvider } from '#vue/apollo-option'
const cache = new InMemoryCache()
const apolloClient = new ApolloClient({
cache,
uri: 'https://rickandmortyapi.com/graphql',
})
const apolloProvider = createApolloProvider({
defaultClient: apolloClient,
})
const app = createApp({
setup () {
provide(DefaultApolloClient, apolloClient)
},
render: () => h(App),
})
app.use(apolloProvider)
app.use(createPinia());
app.use(router);
app.mount('#app');
With the <script setup></script> it is showing this error?
screenshot of error
Seems like you made mistake during copypasting.
Replace this:
apollo: gql`
query Characters {
characters {
results {
id
name
image
}
}
}
`
With this:
const CHARACTERS_QUERY = gql`
query Characters {
characters {
results {
id
name
image
}
}
}
`

Vuex: cannot get store properly initialised

I have a vue3 application and try to get vuex 4 up and running.
This is my main.js:
import { createApp } from 'vue'
import App from './App.vue'
import 'leaflet/dist/leaflet.css';
import { Icon } from 'leaflet';
import mitt from 'mitt';
import store from './store';
/* Theme variables */
import './theme/variables.css';
delete Icon.Default.prototype._getIconUrl;
Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
});
export const eventHub = mitt();
const app = createApp(App)
.use(IonicVue)
.use(store);
app.mount('#app');
My store.js:
import { createStore } from 'vuex'
export const store = createStore({
state() {
return {
aantalBankjes: 0
}
},
mutations: {
vernieuwAantalBankjes(state, n) {
// mutate state
state.aantalBankjes = n;
console.log("Store: vernieuwAantalBankjes");
console.log(n);
}
},
getters: {
getAantalBankjes(state) {
return state.aantalBankjes;
}
}
})
I then want to use the vuex store in a component. The most important code of this component:
<template>
<div id="myMap" style="width: 100%; height: 100%"></div>
</template>
<script>
import L from "leaflet";
import { eventHub } from "../main";
import { Geolocation } from "#capacitor/geolocation";
import icons from "../mixins/icons.js";
const axios = require("axios");
export default {
methods: {
getBankjes() {
let vertices = this.calculateRetrievalArea(this.map.getCenter());
axios
.get(
"https://www.evenuitrusten.nl/api/area?lngLow=" +
vertices.lngLow +
"&lngHigh=" +
vertices.lngHigh +
"&latLow=" +
vertices.latLow +
"&latHigh=" +
vertices.latHigh +
"&number=200"
)
// .get("https://www.evenuitrusten.nl/api/area/test")
.then((response) => {
this.bankjes = response.data;
console.log("Bankjes: axios has returned data");
this.placeMarkers(this.bankjes);
this.aantalBankjes=this.bankjes.length;
console.log("Before commit");
this.$store.commit('vernieuwAantalBankjes',this.aantalBankjes);
console.log("After commit");
return this.bankjes;
})
.catch(function (error) {
// handle error
console.log("bankjes:" + error.response);
});
},
I run into a problem at this.$store.commit. I never reach the line " console.log("After commit");". Instead the catch-function is hit. There is no error message in the console.
What am I doing wrong?
Kind regards,
Hubert
I am new in Vue... but i read that "Mutations Must Be Synchronous"... and you call it in asynchronous code. So, create an action witch will call the mutation and you call the action.
actions: {
act_vernieuwAantalBankjes({commit}, data) {
commit('vernieuwAantalBankjes',data);
}
}
and call the action in the asynchronous code:
this.$store.dispatch('act_vernieuwAantalBankjes',this.aantalBankjes);

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>