Upgrading to Vue 3: where to put data and methods? - vue.js

I am following an upgrade guide on how to go from Vue to Vue3. It shows how to handle it if the app is structured like:
new Vue({
router,
render: h => h(App)
}).$mount("#app");
The problem is that my my app is structured like this:
new Vue({
el: '#app',
data() {
return {
// initialData
};
},
mounted() {
// mounted
},
methods: {
}
}
Where do I place the data, mounted, methods, etc to have it work with the new structure in Vue 3?

You could import h to render the App component and use your usual options :
import {createApp,h} from 'vue'
...
createApp({
data() {
return {
// initialData
};
},
mounted() {
// mounted
},
methods: {
},
render: () => h(App)
})

isn't it the same as just creating an App Component?
const app = createApp(App);
app.mount("#app");
in the App Component
import { defineComponent, onMounted } from "vue";
export default defineComponent({
name: "App",
components: {},
setup() {
const initialData = "";
onMounted(() => {
console.log("mounted");
});
const aMethod = () => {
return null;
};
return {
initialData,
aMethod
};
}
});
</script>

Related

Vue3 reactive components on globalProperties

In vuejs 2 it's possible to assign components to global variables on the main app instance like this...
const app = new Vue({});
Vue.use({
install(Vue) {
Vue.prototype.$counter = new Vue({
data: () => ({ value: 1 }),
methods: {
increment() { this.value++ },
}
});
}
})
app.$mount('#app');
But when I convert that to vue3 I can't access any of the properties or methods...
const app = Vue.createApp({});
app.use({
install(app) {
app.config.globalProperties.$counter = Vue.createApp({
data: () => ({ value: 1 }),
methods: {
increment() { this.value++ }
}
});
}
})
app.mount('#app');
Here is an example for vue2... https://jsfiddle.net/Lg49anzh/
And here is the vue3 version... https://jsfiddle.net/Lathvj29/
So I'm wondering if and how this is still possible in vue3 or do i need to refactor all my plugins?
I tried to keep the example as simple as possible to illustrate the problem but if you need more information just let me know.
Vue.createApp() creates an application instance, which is separate from the root component of the application.
A quick fix is to mount the application instance to get the root component:
import { createApp } from 'vue';
app.config.globalProperties.$counter = createApp({
data: () => ({ value: 1 }),
methods: {
increment() { this.value++ }
}
}).mount(document.createElement('div')); 👈
demo 1
However, a more idiomatic and simpler solution is to use a ref:
import { ref } from 'vue';
const counter = ref(1);
app.config.globalProperties.$counter = {
value: counter,
increment() { counter.value++ }
};
demo 2
Not an exact answer to the question but related. Here is a simple way of sharing global vars between components.
In my main app file I added the variable $navigationProps to global scrope:
let app=createApp(App)
app.config.globalProperties.$navigationProps = {mobileMenuClosed: false, closeIconHidden:false };
app.use(router)
app.mount('#app')
Then in any component where I needed that $navigationProps to work with 2 way binding:
<script>
import { defineComponent, getCurrentInstance } from "vue";
export default defineComponent({
data: () => ({
navigationProps:
getCurrentInstance().appContext.config.globalProperties.$navigationProps,
}),
methods: {
toggleMobileMenu(event) {
this.navigationProps.mobileMenuClosed =
!this.navigationProps.mobileMenuClosed;
},
hideMobileMenu(event) {
this.navigationProps.mobileMenuClosed = true;
},
},
Worked like a charm for me.
The above technique worked for me to make global components (with only one instance in the root component). For example, components like Loaders or Alerts are good examples.
Loader.vue
...
mounted() {
const currentInstance = getCurrentInstance();
if (currentInstance) {
currentInstance.appContext.config.globalProperties.$loader = this;
}
},
...
AlertMessage.vue
...
mounted() {
const currentInstance = getCurrentInstance();
if (currentInstance) {
currentInstance.appContext.config.globalProperties.$alert = this;
}
},
...
So, in the root component of your app, you have to instance your global components, as shown:
App.vue
<template>
<v-app id="allPageView">
<router-view name="allPageView" v-slot="{Component}">
<transition :name="$router.currentRoute.name">
<component :is="Component"/>
</transition>
</router-view>
<alert-message/> //here
<loader/> //here
</v-app>
</template>
<script lang="ts">
import AlertMessage from './components/Utilities/Alerts/AlertMessage.vue';
import Loader from './components/Utilities/Loaders/Loader.vue';
export default {
name: 'App',
components: { AlertMessage, Loader }
};
</script>
Finally, in this way you can your component in whatever other components, for example:
Login.vue
...
async login() {
if (await this.isFormValid(this.$refs.loginObserver as FormContext)) {
this.$loader.activate('Logging in. . .');
Meteor.loginWithPassword(this.user.userOrEmail, this.user.password, (err: Meteor.Error | any) => {
this.$loader.deactivate();
if (err) {
console.error('Error in login: ', err);
if (err.error === '403') {
this.$alert.showAlertFull('mdi-close-circle', 'warning', err.reason,
'', 5000, 'center', 'bottom');
} else {
this.$alert.showAlertFull('mdi-close-circle', 'error', 'Incorrect credentials');
}
this.authError(err.error);
this.error = true;
} else {
this.successLogin();
}
});
...
In this way, you can avoid importing those components in every component.

Vue-router: Using component method within the router

My first Vue project and I want to run a loading effect on every router call.
I made a Loading component:
<template>
<b-loading :is-full-page="isFullPage" :active.sync="isLoading" :can-cancel="true"></b-loading>
</template>
<script>
export default {
data() {
return {
isLoading: false,
isFullPage: true
}
},
methods: {
openLoading() {
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 10 * 1000)
}
}
}
</script>
And I wanted to place inside the router like this:
router.beforeEach((to, from, next) => {
if (to.name) {
Loading.openLoading()
}
next()
}
But I got this error:
TypeError: "_components_includes_Loading__WEBPACK_IMPORTED_MODULE_9__.default.openLoading is not a function"
What should I do?
Vuex is a good point. But for simplicity you can watch $route in your component, and show your loader when the $route changed, like this:
...
watch: {
'$route'() {
this.openLoading()
},
},
...
I think it's fast and short solution.
I don't think you can access a component method inside a navigation guard (beforeEach) i would suggest using Vuex which is a vue plugin for data management and then making isLoading a global variable so before each route navigation you would do the same ... here is how you can do it :
Of course you need to install Vuex first with npm i vuex ... after that :
on your main file where you are initializing your Vue instance :
import Vue from 'vue'
import Vuex from 'vue'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
isLoading: false,
},
mutations: {
openLoading(state) {
state.isLoading = true
setTimeout(() => {
state.isLoading = false
}, 10000)
},
},
})
// if your router is on a separated file just export the store and import it there
const router = new VueRouter({
routes: [
{
// ...
},
],
})
router.beforeEach((to, from, next) => {
if (to.name) {
store.commit('openLoading')
}
next()
})
new Vue({
/// ....
router,
store,
})
In your component:
<b-loading :is-full-page="isFullPage" :active.sync="$store.state.isLoading" :can-cancel="true"></b-loading>

How to load data before create VueJS app?

I'm a little confused. How to load data (main.js file) and afrer (inside a component) set this data to data() function (calc.js)?
I have the data.json file:
{
"store_data": "VUE_STORE",
}
I have the store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
url_server: 'data.json',
store_data: '',
},
actions: {
getServerData({commit}){
return new Promise((resolve, reject) => {
Vue.http.get(this.state.url_server).then(function (response) {
if (response.status == "200") {
commit('LOAD_SERVER_DATA', response)
resolve()
}
});
});
}
},
mutations: {
LOAD_SERVER_DATA (state, response) {
this.store_data = response.data.store_data;
},
},
});
I have the main.js file:
import Vue from 'vue';
import VueResource from 'vue-resource';
import { store } from './store/store';
Vue.config.productionTip = false;
import calc from './components/calc/calc';
Vue.use(VueResource);
var app = new Vue({
el: '#app',
store,
data: {},
components: {
'calc': calc,
},
beforeCreate() {
this.$store.dispatch('getServerData');
}
});
And the component file calc.js
module.exports = {
name: 'calc',
template: `
<div>
<h1>calc</h1>
<h2>{{test_value}}</h2>
</div>
`,
data() {
return {
test_value: 'AAA',
}
},
methods: {
updateTimer() {
},
},
created() {
this.test_value = this.$store.state.store_data;
/* this.$store.dispatch('getServerData').then(() => {
this.test_value = this.$store.state.store_data;
console.log(this.$store.state.store_data);
});*/
},
computed: {
},
mounted() {
},
};
I'd like to set a test_value in calc.js file value this.$store.state.store_data. How it is possible?
Don't use data for data owned by the store. Use computed to return the store value, like so
created() {
this.$store.dispatch('getServerData');
},
computed: {
test_value(){
return this.$store.state.store_data;
}
},
mounted() {
},
And then in the vuex store the mutation has a little bug
mutations: {
LOAD_SERVER_DATA (state, response) {
state.store_data = response.data.store_data;
},

Vuex child components cannot access this.$store (undefined)

I followed these instructions in the Vuex documentation for accessing the Vuex state from my Vue components... but whenever I use this.$store.something in my components, I get TypeError: Cannot read property 'something' of undefined (see screenshot at the bottom of this post).
The documentation says,
By providing the store option to the root instance, the store will be
injected into all child components of the root and will be available
on them as this.$store
...but that functionality does not seem to be working in my application.
Here is my code:
main.js
import Vue from 'vue'
import App from './App'
import axios from 'axios'
import router from './router'
import store from './store'
Vue.config.productionTip = false
axios.defaults.baseURL = 'http://localhost:3000'
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
store.js
import Vue from 'Vue'
import Vuex from 'vuex'
import router from './router'
import axios from 'axios'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
token: null
},
mutations: { // setters (synchronous)
setToken (state, userData) {
state.token = userData.token
},
clearToken (state) {
state.token = null
}
},
actions: { // asynchronous tasks
signup (authData) {
axios.post('/user/signup', {
email: authData.email,
password: authData.password
})
.then(res => {
if (res.status === 201) {
// what happens if signup succeeds?
} else {
// what happens if signup fails?
}
})
.catch(error => console.log(error))
},
setLogoutTimer ({commit}, expiresIn) {
setTimeout(() => {
commit('clearToken')
}, expiresIn * 1000)
},
login ({commit, dispatch}, authData) {
axios.post('/user/login', {
email: authData.email,
password: authData.password
})
.then(res => {
console.log(res)
// set token with timeout
const now = new Date()
const tokenExpiration = new Date(now.getTime() + res.data.expiresIn * 1000)
localStorage.setItem('token', res.data.token)
localStorage.setItem('tokenExpiration', tokenExpiration)
commit('setToken', { token: res.data.token })
dispatch('setLogoutTimer', res.data.expiresIn)
// redirect to dashboard
router.replace('/dashboard')
})
.catch(error => console.log(error))
},
tryAutoLogin ({commit}) {
const token = localStorage.getItem('token')
if (!token) {
return
}
const tokenExpiration = localStorage.getItem('tokenExpiration')
const now = new Date()
if (now >= tokenExpiration) {
return
}
commit('setToken', { token: token })
},
logout ({commit}) {
commit('clearToken')
localStorage.removeItem('token')
localStorage.removeItem('tokenExpiration')
router.replace('/login')
}
},
getters: {
isAuthenticated (state) {
return state.token !== null
}
}
})
App.vue
<template>
<div id="app">
<app-header/>
<router-view/>
</div>
</template>
<script>
import Header from './components/Header.vue'
export default {
name: 'App',
components: {
'app-header': Header
},
created () {
this.$store.dispatch('tryAutoLogin')
}
}
</script>
Header.vue
<template>
<header id="header">
<div class="logo">
<router-link to="/">Home</router-link>
</div>
<nav>
<ul>
<li v-if="!auth">
<router-link to="/signup">Sign Up</router-link>
</li>
<li v-if="!auth">
<router-link to="/login">Login</router-link>
</li>
<li v-if="auth">
<router-link to="/dashboard">Dashboard</router-link>
</li>
<li v-if="auth">
<a #click="onLogout">Logout</a>
</li>
</ul>
</nav>
</header>
</template>
<script>
export default {
computed: {
auth () {
return this.$store.state.token !== null
}
},
methods: {
onLogout () {
this.$store.dispatch('logout')
}
},
watch: {
$route () {
console.log('STORE: ', this.$store.state)
}
}
}
</script>
The errors:
Use Destructuring when importing store into your main.js file.
Change your code from
import Vue from 'vue'
import App from './App'
import axios from 'axios'
import router from './router'
import store from './store'
Vue.config.productionTip = false
axios.defaults.baseURL = 'http://localhost:3000'
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
To
import Vue from 'vue'
import App from './App'
import axios from 'axios'
import router from './router'
import { store } from './store' //Added Destructuring
Vue.config.productionTip = false
axios.defaults.baseURL = 'http://localhost:3000'
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
It worked for me..Hope it works for you as well!
Everything looks good in your code. However in your main.js file change the following code
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
to
new Vue({
el: "#app",
router,
store,
render: h => h(App)
});

trying to add user input to api using axios in vue js but having an error?

//this is my signupform.js where i have an object which have my form keys
import Datepicker from 'vuejs-datepicker'
import store from '../../store';
export default {
name: 'Signupform',
components: {
Datepicker,store
},
data() {
return {
enter_details:
{
username: '',
email: '',
contactNumber: '',
firstName: '',
lastName:'',
dob: '',
password: '',
repeat_password: ''
}
}
},
methods:{
addtoAPI() {
this.$store.dispatch('addtoapi',this.enter_details)
}
}
};
//this is my store's action
import vuex from 'vuex';
import axios from 'axios'
vue.use(vuex);
const store = new vuex.Store({
actions: {
addtoapi: ({commit}, enter_details) => {
let newuser = {
username: enter_details.username,
email: enter_details.email,
contactNumber: enter_details.contactNumber,
firstName: enter_details.firstName,
lastName: enter_details.lastName,
dob: enter_details.dob,
password: enter_details.password,
repeat_password: enter_details.repeat_password,
}
console.log(newuser);
axios.post('https://dev-api.mysc.io/int/api/v1', newuser)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
})
}
}
});
//now i am getting an error i.e
Signupform.js?22e4:28 Uncaught TypeError: this.$store.dispatch is not a function
at VueComponent.addtoAPI (Signupform.js?22e4:28)
at boundFn (vue.esm.js?efeb:190)
at invoker (vue.esm.js?efeb:2004)
at HTMLButtonElement.fn._withTask.fn._withTask
i am also getting one more error that when i try to see my store on vue on my browser it shows that "no vuex store"
please help me to resolve this error because i have alreaady
//this is my main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
Vue.config.productionTip = false
/* eslint-disable no-new */
export const bus = new Vue();
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
In your store.js write this:
export default new Vuex.Store({
//
});
instead of
export default({
//
});
UPD: demo
And you don't need to include store as a component:
// signupform.js file ...
components: {
Datepicker,
store // <--- this is unnessesary
},
const store = new Vuex.Store({
actions: {
theAction() {
alert('Action fired');
},
},
});
const app = new Vue({
el: "#app",
store,
methods: {
fireAction() {
this.$store.dispatch('theAction')
},
},
})
<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<div id="app">
<button #click="fireAction">Press me</button>
</div>