VUE 3 JS : can't acces to my props in mounted - vuex

I have a problem in a component.
I receive an id (name : theIdPost) from a parent file of this component but when I would like to use it in the mounted(){} part , it tells me :
TS2339: Property 'theIdPost' does not exist on type '{...
I can print the id in template, no worries but to use it in the SCRIPT part it doesn't work.
the component file:
<template lang="fr">
// All my html
</template>
<script lang="ts">
import { computed } from 'vue';
import { store } from '../store/index';
export default{
name: 'comment',
props: {
theIdPost: Number,
theTxtPost: String,
theLike: Number,
},
setup() {
const myStore: any = store
const commentList = computed(() => myStore.state.commentList);
console.log("CommentList > " +commentList.value);
return { commentList };
},
mounted() {
const myStore: any = store;
myStore.dispatch("getComments",
{'id': this.theIdPost}
);
}
}
</script>
<style lang="scss">
#import "../scss/variables.scss";
// ..... the style part
</style>
Can you explain me why it doesn't work ?
Thanks

If you are using the composition API with the setup, you have to add the lifecycle hooks differently:
https://v3.vuejs.org/guide/composition-api-lifecycle-hooks.html
setup(props) {
const myStore: any = store
const commentList = computed(() => myStore.state.commentList);
console.log("CommentList > " +commentList.value);
onMounted(() => {
myStore.dispatch("getComments",
{'id': props.theIdPost}
);
})
return { commentList };
},

For Solution there is 2 points :
because I use vue 3 and setup in composition API , the lifecycle Hook is different and mounted => onMounted
setup(props) {
const myStore: any = store
const commentList = computed(() => myStore.state.commentList);
onMounted(() => {
myStore.dispatch("getComments",
{'id': props.theIdPost}
);
})
return { commentList };
},
when we use onMounted, is like when we use ref(), we have to import before. So at the beginning of the SCRIPT part, we have to write :
import { onMounted } from 'vue';
So my final script is :
<script lang="ts">
import { computed, onMounted } from 'vue';
import { store } from '../store/index';
export default {
name: 'comment',
props: {
theIdPost: Number,
theTxtPost: String,
theLike: Number,
},
setup(props) {
const myStore: any = store;
const commentList = computed(() => myStore.state.commentList);
onMounted(() => {
myStore.dispatch("getComments",
{ 'id': props.theIdPost }
);
})
return { commentList };
},
}
</script>
Thanks to Thomas for the beginning of the answer :)

it worked for me too. i was setting up the setup and not pass props in to the setup. now okay

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 3 - Composition API fetching data?

I am a bit confused with composition API and fetching data. When I open the page, I can see rendered list of categories, but if I want to use categories in setup(), it is undefined. How can I use categories value inside setup function? You can see that I want to console log categories.
Category.vue
<template>
<div class="page-container">
<item
v-for="(category, index) in categories"
:key="index"
:item="category"
:is-selected="selectedItem === index"
#click="selectItem(index)"
/>
</div>
</template>
<script>
import { computed, ref } from 'vue'
import { useStore } from 'vuex'
import Item from '#/components/Item.vue'
export default {
components: {
Item
},
setup () {
const store = useStore()
store.dispatch('categories/getCategories')
const categories = computed(() => store.getters['categories/getCategories'])
const selectedItem = ref(1)
const selectItem = (index) => {
selectedItem.value = index
}
console.log(categories.value[selectedItem.value].id)
return {
categories,
selectedItem,
selectItem
}
}
}
</script>
<style lang="scss" scoped>
#import '#/assets/scss/general.scss';
</style>
categories.js - vuex module
import axios from 'axios'
import { API_URL } from '#/helpers/helpers'
export const categories = {
namespaced: true,
state: {
categories: []
},
getters: {
getCategories: (state) => state.categories
},
mutations: {
UPDATE_CATEGORIES: (state, newValue) => { state.categories = newValue }
},
actions: {
async getCategories ({ commit }) {
await axios.get(`${API_URL}/getCategories.php`).then(response => {
commit('UPDATE_CATEGORIES', response.data.res_data.categories)
})
}
},
modules: {
}
}
In the setup function you cannot process a computed function.
You can instead access store.getters['categories/getCategories'].value[selectedItem.value].id if you want to process that in the setup function.

How to use composition API to create a new component in vue3?

When we use vue2 to create API, we just follow options API like below:
data are in data
methods are in methods
<script>
export default {
name: 'demo',
components: {},
filter:{},
mixins:{},
props: {},
data(){
return{
}
},
computed:{},
watch:{},
methods: {},
}
</script>
But the vue3 changed, how should I build a component with vue3 composition API?
Some example say that I should import reactive etc. From vue first and put all codes in setup(){}?
Some example show that I can add setup to <script>?
Please give me an example.
ok bro , Composition Api works like that:
<script>
import { fetchTodoRepo } from '#/api/repos'
import {ref,onMounted} from 'vue'
export default {
setup(props){
const arr = ref([]) // Reactive Reference `arr`
const getTodoRepo = async () => {
arr.value = await fetchTodoRepo(props.todo)
}
onMounted(getUserRepo) // on `mounted` call `getUserRepo`
return{
arr,
getTodoRepo
}
}
}
</script>
There are two ways to create a component in vue3.
One:<script> + setup(){},such as this:
<script>
import { reactive, onMounted, computed } from 'vue'
export default {
props: {
title: String
},
setup (props, { emit }) {
const state = reactive({
username: '',
password: '',
lowerCaseUsername: computed(() => state.username.toLowerCase())
})
onMounted(() => {
console.log('title: ' + props.title)
})
const login = () => {
emit('login', {
username: state.username,
password: state.password
})
}
return {
login,
state
}
}
}
</script>
Two:use <script setup="props">
loading....

How to use router in vue composition api?

I defined a route in vue:
/users/:userId
Which point to UserComponent:
<template>
<div>{{username}}</div>
</template>
and I use computed from #vue/composition-api to get the data.
the problem is when the route change to another userId, by navigate to another user, the user in the html template not changed as what I expected. also it doesn't do redirect when the the user is not in the list.
So what I can do to fix that?
here is my code:
<template>
<div>{{username}}</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, getCurrentInstance } from '#vue/composition-api';
export const useUsername = ({ user }) => {
return { username: user.name };
};
export default defineComponent({
setup(props, { root }) {
const vm = getCurrentInstance();
const userToLoad = computed(() => root.$route.params.userId);
const listOfUsers = [
{ userId: 1, name: 'user1' },
{ userId: 2, name: 'user2' },
];
const user = listOfUsers.find((u) => u.userId === +userToLoad.value);
if (!user) {
return root.$router.push('/404');
}
const { username } = useUsername({ user });
return { username };
},
});
</script>
You can just do this:
import { useRoute } from 'vue-router';
export default {
setup() {
const route = useRoute();
// Now you can access params like:
console.log(route.params.id);
}
};
From the vue-router documentation:
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
function pushWithQuery(query) {
if (!user) {
router.push({
name: '404',
query: {
...route.query
}
})
}
}
}
}
You can pass the parameters as props to your components. Props are reactive by default.
This is how the route configuration could look like:
{
path: '/users/:userId',
name: Users,
component: YourComponent
},
You can then use the props in your component with watchEffect()
<template>
<div>{{username}}</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, getCurrentInstance, watchEffect } from '#vue/composition-api';
export const useUsername = ({ user }) => {
return { username: user.name };
};
export default defineComponent({
props: {userId: {type: String, required: true },
setup(props, { root }) {
const vm = getCurrentInstance();
const user = ref()
const userToLoad = computed(() => props.userId);
const listOfUsers = [
{ userId: 1, name: 'user1' },
{ userId: 2, name: 'user2' },
];
watchEffect(() => user.value = listOfUsers.find((u) => u.userId === +userToLoad.value))
if (!user) {
return root.$router.push('/404');
}
const { username } = useUsername({ user });
return { username };
},
});
</script>
watchEffect() will run immediately when defined and when reactive dependencies.change
A had the same problem. I use vue 2 and #vue/composition-api
My resolution:
Created: src/router/migrateRouterVue3.js
import { reactive } from '#vue/composition-api';
import router from './index';
const currentRoute = reactive({
...router.currentRoute,
});
router.beforeEach((to, from, next) => {
Object.keys(to).forEach(key => {
currentRoute[key] = to[key];
});
next();
});
// eslint-disable-next-line import/prefer-default-export
export function useRoute() {
return currentRoute;
}
after that, I can usage:
// import { useRoute } from 'vue-router';
import { useRoute } from '#/router/migrateRouterVue3';
Resolution for you:
// replace:
// const userToLoad = computed(() => root.$route.params.userId);
// to:
import { useRoute } from '#/router/migrateRouterVue3';
//...
const route = useRoute();
const userToLoad = computed(() => route.params.userId);
function useRoute() {
const vm = getCurrentInstance()
if (!vm) throw new Error('must be called in setup')
return vm.proxy.$route
}
https://github.com/vuejs/composition-api/issues/630
The following useRoute hook will make route reactive so that it's doable:
const route = useRoute();
const fooId = computed(()=>route.params.fooId);
let currentRoute = null;
export const useRoute = () => {
const self = getCurrentInstance();
const router = self.proxy.$router;
if (!currentRoute) {
const route = { ...self.proxy.$route };
const routeRef = shallowRef(route);
const computedRoute = {};
for (const key of Object.keys(routeRef.value)) {
computedRoute[key] = computed(() => routeRef.value[key]);
}
router.afterEach((to) => {
routeRef.value = to;
});
currentRoute = reactive(computedRoute);
}
return currentRoute;
};
The vue2-helpers package provides a useRoute function you can use in Vue 2.7 (and 2.6, 2.5 also).
Installation
# Vue 2.7
$ npm install vue2-helpers#2
# Vue 2.5 and 2.6
$ npm install vue2-helpers#1
Usage
import { useRoute } from 'vue2-helpers/vue-router';
const route = useRoute();
const id: string | undefined = route.params.id;
const { proxy } = getCurrentInstance();
then use proxy to access $router or $route
Add please this code: watchEffect(() => userToLoad);

How to get current name of route in Vue?

I want to get the name of the current route of vue-router, i have a component menu with navigation to another componentes, so i want to dispaly the name of the current route.
I have this:
created(){
this.currentRoute;
//this.nombreRuta = this.$route.name;
},
computed:{
currentRoute:{
get(){
this.nombreRuta = this.$route.name;
}
}
}
But the label of the name of the route does not change, the label only show the name of the first loaded route.
Thank You
EDIT:
Image to show what i want
You are using computed incorrectly. You should return the property in the function. See the docs for more information.
Here is your adapted example:
computed: {
currentRouteName() {
return this.$route.name;
}
}
You can then use it like this:
<div>{{ currentRouteName }}</div>
You can also use it directly in the template without using a computed property, like this:
<div>{{ $route.name }}</div>
Vue 3 + Vue Router 4
Update 5/03/2021
If you are using Vue 3 and Vue Router 4, here is two simplest ways to get current name of route in setup hook:
Solution 1: Use useRoute
import { useRoute } from 'vue-router';
export default {
setup () {
const route = useRoute()
const currentRouteName = computed(() => route.name)
return { currentRouteName }
}
}
Solution 2: Use useRouter
import { useRouter } from 'vue-router';
export default {
setup () {
const router = useRouter()
const currentRouteName = computed(() => router.currentRoute.value.name;)
return { currentRouteName }
}
}
I use this...
this.$router.history.current.path
In Composition API, this works
import { useRouter } from 'vue-router'
const router = useRouter()
let currentPathObject = router.currentRoute.value;
console.log("Route Object", currentPathObject)
// Pick the values you need from the object
I used something like this:
import { useRoute } from 'vue-router';
then declared
const route = useRoute();
Finally if you log route object - you will get all properties I used path for my goal.
This is how you can access AND watch current route's name using #vue/composition-api package with Vue 2 in TypeScript.
<script lang="ts">
import { defineComponent, watch } from '#vue/composition-api';
export default defineComponent({
name: 'MyCoolComponent',
setup(_, { root }) {
console.debug('current route name', root.$route.name);
watch(() => root.$route.name, () => {
console.debug(`MyCoolComponent- watch root.$route.name changed to ${root.$route.name}`);
});
},
});
</script>
I will update this answer once Vue 3.0 and Router 4.0 gets released!
I use this...
this.$route.name
In my Laravel app I created a router.js file and I can access the router object in any vue component like this.$route
I usually get the route like this.$route.path
Using composition API,
<template>
<h1>{{Route.name}}</h1>
</template>
<script setup>
import {useRoute} from 'vue-router';
const Route = useRoute();
</script>
Using Vue 3 and Vue Router 4 with Composition API and computed:
<script setup>
import { computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// computed
const currentRoute = computed(() => {
return router.currentRoute.value.name
})
</script>
<template>
<div>{{ currentRoute }}</div>
</template>
âš  If you don't set a name in your router like so, no name will be displayed:
const routes = [
{ path: '/step1', name: 'Step1', component: Step1 },
{ path: '/step2', name: 'Step2', component: Step2 },
];
In Vue 3.2 using Composition API
<script lang="ts" setup>
import { useRoute } from "vue-router";
const route = useRoute();
const currentRouteName = computed(() => {
return route.name;
});
</script>
<template>
<div>
Using computed:{{currentRouteName}}
or without using computed: {{route.name}}
</div>
</template>
This is how you can get id (name) of current page in composition api (vue3):
import { useRoute } from 'vue-router';
export function useFetchPost() {
const currentId = useRoute().params.id;
const postTitle = ref('');
const fetchPost = async () => {
try {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${currentId}`
);
postTitle.value = response.data.title;
} catch (error) {
console.log(error);
} finally {
}
};
onMounted(fetchPost);
return {
postTitle,
};
}
I'm using this method on vue 3 & vue-router 4
It works great!
<script>
import { useRoute } from 'vue-router'
export default {
name: 'Home',
setup() {
const route = useRoute();
const routeName = route.path.slice(1); //route.path will return /name
return {
routeName
}
}
};
</script>
<p>This is <span>{{ routeName }}</span></p>
I've Tried and it Worked:
Use Following in Your Elements;
{{ this.$route.path.slice(1) }}
this.$router.currentRoute.value.name;
Works just like this.$route.name.
Vue 3 + Vue Router 4 + Pinia store (or any other place outside of vue components)
#KitKit up there gave an example how to get route if you are using Vue 3 and Vue Router 4 in setup hook. However, what about state management in Pinia store ?
In vue#2 and vue-router#3.5.1: We could have used router.currentRoute.query.returnUrl like so (example in vuex state management):
import router from "#/router";
const state = initialState;
const getters = {};
const actions = { // your actions };
const mutations = {
loginSuccess(state, user) {
let returnUrl = "";
if(router.currentRoute.query.returnUrl != undefined)
returnUrl = router.currentRoute.query.returnUrl;
},
};
export default {
state,
getters,
actions,
mutations,
};
export const authentication = {
actions: {},
mutations: {},
};
In vue#3 and vue-router#4: We have to append value to currentRoute like so:
import router from '#/router';
export const authenticationStore = defineStore('authUser', {
state: (): State => ({
// your state
}),
getters: {
// your getters
},
actions: {
loginSuccess(user: object) {
let returnUrl = '';
if (router.currentRoute.value.query.returnUrl != undefined)
returnUrl = router.currentRoute.value.query.returnUrl;
},
},
});