Having difficulty setting up pinia stores in nuxt 3 - vuejs3

I'm currently trying to setup a project using nuxt 3 with pinia for state management and I have bumped into the following error:
[h3] [unhandled] H3Error: defineStore is not defined
at createError (file:///home/johnr/Code/Personal/test/node_modules/h3/dist/index.mjs:191:15)
at Server.nodeHandler (file:///home/johnr/Code/Personal/test/node_modules/h3/dist/index.mjs:381:21) {
statusCode: 500,
fatal: false,
unhandled: true,
statusMessage: 'Internal Server Error'
}
I initialized the project with npx nuxi init and then ran npm i, followed by npm install #pinia/nuxt. I then added pinia to nuxt.config.ts:
// nuxt.config.js
export default {
// ... other options
modules: [
// ...
'#pinia/nuxt',
],
}
and created a basic store in store/counter.js:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
function increment() {
count.value++;
}
return { count, increment };
});
and have tried to use the returned count in the app template:
<template>
<div>
<p>The count is {{ counterStore.count.value }}</p>
</div>
</template>
<script setup>
import { useCounterStore } from './store/counter.js';
const counterStore = useCounterStore();
</script>

It looks like you forgot to import defineStore in store/counter.js:
import { defineStore } from 'pinia'

Related

Vue3 Composition API - Pinia store returns "Uncaught (in promise) TypeError: ____ is not a function"

Searched all over SO, very little information.
The store:
import { defineStore } from 'pinia'
import axios from 'axios'
export const useScanStore = defineStore({
id: 'scan',
state: () => ({
scans: [],
scan : null,
loading: false,
error: null,
messages : []
}),
getters: {
getScans : state => state.scans,
},
subscriptions: {
},
actions: {
async updateScan(id,data) {
// code here that works
}
// trimmed for shortness
}
THE COMPONENT
<template>
<div>
<q-btn color="orange" text-color="white" size="sm" #click="stopScan" class="q-mr-sm">
STOP
</q-btn>
</div>
</template>
<script setup>
import {useScanStore} from "#/stores/scan";
const scanStore = useScanStore();
// trimmed
async function stopScan(id){
const rerunResponse = await scanStore.updateScan(id, {status: 'failed'}) //<<< ERROR HERE
}
</script>
The frustrating part is that THIS EXACT code works in other component. So that function DOES exist.
It's imported IDENTICALLY in component where it works.
Error points at that line in function.
Why am I getting this error? I reduced component to just a single function, button and call.
There are NO OTHER ERRORS in console. Just this (when button is pressed):
I ended up destroying and rebuilding my docker compose (Docker image) and it started working.

Vue 3 script setup components not recognized by IDE

I have created npm library that is installed on another project through package.json. The library is created in Vue3. The problem is that components that are created with script setup are not recognized by IDE (WebStorm) and components that are created with Options API (without script setup) are recognized. Both components works, but the problem is that for example test-input component is not recognized and test-button components is.
vite.config.ts
build: {
cssCodeSplit: false,
lib: {
entry: './src/TestDesignSystemPlugin.ts',
formats: ['es', 'cjs'],
name: 'TestDesignSystemPlugin',
fileName: (format) => (format === 'es' ? 'index.js' : 'index.cjs'),
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue',
},
},
},
},
TestButton.vue - Component created with Options API
<template>
<button>
<slot />
</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'TestButton',
})
</script>
TestInput.vue - Component created with Composition API
<template>
<input v-model="model" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
const model = ref('')
</script>
Also this is TestDesignSystemPlugin.ts file that is used in vite.config.ts and where components are installed:
import type { App } from 'vue'
import {
TestButton,
TestInput,
} from '#/components'
export default {
install: (app: App) => {
app.component('TestButton', TestButton)
app.component('TestInput', TestInput)
},
}
UPDATE:
I don't think that issue is with IDE, than with the build of the library (plugin). When I see index.js of the build I see the difference between components that are with setup and without setup:
var TestDesignSystemPlugin = {
install: (app) => {
app.component("TestButton", TestButton);
app.component("TestButtonSocial", TestButtonSocial);
app.component("TestInput", _sfc_main$8);
}
};

How to use SSR with Stencil in a Nuxt 3 Vite project?

In Nuxt 2 I could use server-side rendered Stencil components by leveraging the renderToString() method provided in the Stencil package in combination with a Nuxt hook, like this:
import { renderToString } from '[my-components]/dist-hydrate'
export default function () {
this.nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: false
})
page.html = render.html
})
}
Since the recent release of Stencil 2.16.0 I'm able to use native web components in Nuxt 3 that is powered by Vite. However I haven't found a way to hook into the template hydration process. Unfortunately there is no documentation for the composable useHydration() yet.
Does anybody know how I could get this to work in Nuxt 3?
I had the same problem. I solved it via a module.
Make a new custom nuxt module. documentation for creating a module
In the setup method hook into the generate:page hook:
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
documentation for nuxt hooks
documentation for stencil hydration (renderToString)
Register the css classes you need via nuxt.options.css.push(PATH_TO_CSS)
Register the module in the nuxt config.
Note: Make sure in the nuxt.config.ts the defineNuxtConfig gets exported as default.
Tap the vue compiler options in the nuxt config:
vue: {
compilerOptions: {
isCustomElement: (tag) => TEST_TAG_HERE,
},
},
This depends on how you wan't to use the custom elements. In my case I defined the elements over the stencil loader in my app.vue file:
import { defineCustomElements } from '<package>/<path_to_loader>';
defineCustomElements();
You could also import the elements you need in your component and then define them right there, for example in a example.vue component:
import { CustomElement } from '<package>/custom-elements';
customElements.define('custom-element', CustomElement);
Here is an example from my module and config:
./modules/sdx.ts
import { defineNuxtModule } from '#nuxt/kit';
import { renderToString } from '#swisscom/sdx/hydrate';
export default defineNuxtModule({
meta: {
name: '#nuxt/sdx',
configKey: 'sdx',
},
setup(options, nuxt) {
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
nuxt.options.css.push('#swisscom/sdx/dist/css/webcomponents.css');
nuxt.options.css.push('#swisscom/sdx/dist/css/sdx.css');
},
});
Important: This only works if the stenciljs package supports hydration or in other words has a hydrate output. Read more here
./nuxt.config.ts
import { defineNuxtConfig } from 'nuxt';
//v3.nuxtjs.org/api/configuration/nuxt.config export default
export default defineNuxtConfig({
typescript: { shim: false },
vue: {
compilerOptions: {
isCustomElement: (tag) => /sdx-.+/.test(tag),
},
},
modules: ['./modules/sdx'],
});
./app.vue
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<script setup lang="ts">
import { defineCustomElements } from '#swisscom/sdx/dist/js/webcomponents/loader';
defineCustomElements();
// https://v3.nuxtjs.org/guide/features/head-management/
useHead({
title: 'demo',
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
charset: 'utf-8',
meta: [{ name: 'description', content: 'demo for using a stencil package in a nuxt ssr app' }],
bodyAttrs: {
class: 'sdx',
},
});
</script>
Update
I tested my setup with multiple components and it looks like you cannot define your components in the module. I updated the answer to my working solution.
I've found defining a plugin using the 'render:response' hook to work for me:
server/plugins/ssr-components.plugin.ts
import { renderToString } from '#my-lib/components/hydrate';
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('render:response', async (response) => {
response.body = (await renderToString(response.body)).html;
});
});
Perhaps it will work for you :)
Try this in defineNuxtPlugin
nuxtApp.hook('app:rendered', () => {
const response = nuxtApp.ssrContext?.res
if (!response)
return
const end = response.end
response.end = function(chunk) {
chunk = 'hijacked'
end(chunk)
}
})

Vue 3 extends vue.js components from third party library in defineComponent

I want to use third party library element-plus in my component. In setup defineComponent entends that component. In console, it would warn Failed to resolve component: el-radio at <App>
In about router, Here is the about.vue
<template>
<div id="popup-content"></div>
</template>
<script>
import {
onMounted, createApp, defineComponent, nextTick,
} from 'vue';
import Test from '#/components/Test.vue';
export default {
setup() {
onMounted(() => {
const myNewComponent = defineComponent({
extends: Test,
});
createApp(myNewComponent).mount('#popup-content');
nextTick(() => {
createApp(myNewComponent).mount('#popup-content');
});
});
},
}
Test component has used element-plus el-raido component, Test.vue
<template>
<el-radio v-model="radio" label="1">备选项</el-radio>
<el-radio v-model="radio" label="2">备选项</el-radio>
</template>
<script>
export default {
data() {
return {
radio: '1',
};
},
};
</script>
I have add element-plus, and register all in main.js
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/index.css';
import App from './App.vue';
const app = createApp(App);
app.use(ElementPlus);
app.mount('#app');
I have found this question
Extend vue.js component from third-party library
I really really don't understand what are you trying to achieve by extending your perfectly fine Test component BUT...
Vue 3 is very different from Vue 2 - a lot of global API's (as component registration for example) are not global anymore but are tight to a "app instance" (created by createApp)
So even if you register Element components in main.js (app.use(ElementPlus);), the another app instance (why!?) created in onMounted hook of about.vue component knows nothing about the components! That is the reason for an error...
You must register components in every app instance created by createApp you want to use them in ....
As #Michal Levý answered, I need to register components in every app instance created by createApp.
Here is the working version about.vue, in case someone need.
<template>
<div id="popup-content"></div>
</template>
<script>
import {
onMounted, createApp, defineComponent, nextTick,
} from 'vue';
import Test from '#/components/Test.vue';
import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/index.css';
export default {
setup() {
onMounted(() => {
const myNewComponent = defineComponent({
extends: Test,
});
const app1 = createApp(myNewComponent);
nextTick(() => {
app1.use(ElementPlus);
app1.mount('#popup-content');
});
});
},
}

Vue.js / Firebase and Vuefire - Read and update a single object

I've been trying to set up a simple app to test some Vue.js features and I've been finding here and there some intersting tutorials about basic CRUD implementation.
I've been stuck on something a little different since a few days, here's a simple description of what I try to achieve :
Set up a home page that displays first and last name.
Store first and last name in firebase as strings
Simply display the two strings on screen
later allow the logged-in user to edit the string (not part of my problem here but relevant to explain why I need the two fields to be stored in Firebase)
I've already worked on a small architecture with login management, different menus for logged in/out states, things like that.
So I already set up that in Firebase :
Firebase configuration
Then here my core files :
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import firebase from 'firebase'
import VueFire from 'vuefire'
import { store } from './store/store'
let app
let config = {
apiKey: '######',
authDomain: '######',
databaseURL: '######',
projectId: '######',
storageBucket: '######',
messaginSenderId: '######'
}
firebase.initializeApp(config)
firebase.auth().onAuthStateChanged(function (user) {
if (!app) {
/* eslint-disable no-new */
app = new Vue({
el: '#app',
store: store,
router,
template: '<App/>',
components: { App }
})
}
})
export const db = firebase.database()
export const homeContent = db.ref('homeContent')
Vue.config.productionTip = false
Index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '#/components/HelloWorld'
import Test from '#/components/Test'
import Login from '#/components/Login'
import SignUp from '#/components/SignUp'
import firebase from 'firebase'
import VueFire from 'vuefire'
Vue.use(Router)
Vue.use(VueFire)
let router = new Router({
routes: [
{
path: '*',
redirect: '/login'
},
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/test',
name: 'Test',
component: Test,
meta: {
requiresAuth: true
}
},
{
path: '/sign-up',
name: 'SignUp',
component: SignUp
},
{
path: '/hello-world',
name: 'HelloWorld',
component: HelloWorld,
meta: {
requiresAuth: true
}
}
]
})
router.beforeEach((to, from, next) => {
let currentUser = firebase.auth().currentUser
let requiresAuth = to.matched.some(record => record.meta.requiresAuth)
if (requiresAuth && !currentUser) next('/login')
else if (!requiresAuth && currentUser) next()
else next()
})
export default router
App.vue
<template>
<div id="app">
<div v-if="user">Logged in</div>
<div v-else>NOT logged in</div>
<Navigation></Navigation>
<button id="btLogout" v-if="user" v-on:click="logout">Déconnexion</button>
<img class="logo" src="./assets/logo.png">
<router-view/>
</div>
</template>
<script>
// Register Navbar component
import Navigation from './components/Nav.vue'
import firebase from 'firebase'
export default {
computed: {
user () {
return this.$store.getters.getUser
}
},
components: {
'Navigation': Navigation
},
methods: {
logout: function () {
firebase.auth().signOut().then(() => {
this.$store.dispatch('clearUser')
this.$router.replace('login')
})
},
setUser: function () {
this.$store.dispatch('setUser')
}
},
created () {
// when the app is created run the set user method
// this uses Vuex to check if a user is signed in
// check out mutations in the store.js file
this.setUser()
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.logo {
width: 50px;
height: auto;
clear: both;
display: block;
margin: 30px auto 0;
}
#btLogout {
clear: both;
display: inline-block;
}
</style>
Test.vue
<template>
<div class="homeScreen">
<p v-bind:key="homeContent['.key']" v-for="firstName of homeContent">{{ homeContent.firstName }}</p>
<p v-bind:key="homeContent['.key']" v-for="lastName of homeContent">{{ homeContent.lastName }}</p>
<img src="../assets/annonce_motw.jpg">
</div>
</template>
<!-- Javascript -->
<script>
import firebase from 'firebase'
import db from "../main"
export default {
data () {
return {
db: ''
}
},
firebase: {
homeContent: {
source: db.ref('homeContent'),
asObject: true
}
},
methods: {
setHomeName (key) {
// homeName.child(key).update({ edit: true })
}
},
created () {
}
}
</script>
<!-- SASS styling -->
<style scoped>
</style>
So here I am. The part where I'm stuck is that everytime I try to add in Test.vue the line db.ref('homeContent') the console returns that db is undefined.
I also can't figure how to simply output the stored strings after resolving the console problem.
So what did I do wrong? :D
Thanks and advance for every piece of help you'll bring! Cheers!
The line
export const db = firebase.database()
gets executed before firebase.initializeApp(config) has finished executing, so it will be undefined.
One way to solve this is to put the initialized Firebase object on the Vue prototype:
Vue.prototype.$firebase = Firebase.initializeApp(config)
Then, inside any of your components, such as Test.vue, you can refer to the Firebase object like this:
firebase: {
homeContent: {
source: this.$firebase.database().ref('homeContent'),
asObject: true
}
},
One caveat: This only works if you create the Vue app after you know Firebase has finished initializing. You did this correctly by putting the new Vue() statement inside firebase.auth().onAuthStateChanged() in main.js, so you will be guaranteed to have an initialized Firebase object available to you at this.$firebase in any of your components.

Resources