Vuejs. Update data() on update - vue.js

I'm trying to check if there is an authenticated user in order to render the sidebar and the header.
If it's not authenticated the sidebar and header don't get rendered in order to display just the Login or Register. But my this.auth data is not getting updated until I reload the page. I understand that probably there is a feature on vuejs that will update this variable more often? ( My sidebar contains the router-links to load the rest of my components but this.auth does not get updated when I click on a router-link )
EDITED: Using a computed property solved it.
<template>
<v-app>
<header-component v-if="auth" />
<sidebar-component v-if="auth" />
<router-view></router-view>
</v-app>
</template>
<script>
import Sidebar from "./components/Sidebar"
import Header from "./components/Header"
export default{
name: 'App',
components: {
'sidebar-component': Sidebar,
'header-component': Header,
},
computed: {
auth ()
{
return (!localStorage.getItem("auth")) ? false : true
}
}
}
</script>

You can delete your auth declare in the data, and use the computed like this:
computed {auth (){return (!localStorage.getItem("auth")) ? false : true}}

Related

Vue3 props access inside setup() returns nothing

I have a weird situation with my Vue3 component. I am trying to get a value from the props inside the setup() function. But this returns nothing.
<template>
<div class="border p-2 space-y-2">
<h2 class="text-center">Makers</h2>
{{ product.makers }}
</div>
</template>
<script>
import { ref, onMounted, toRef, toRefs } from 'vue'
import ProductStore from '../store/ProductStore'
export default {
props: {
product: Object
},
setup(props) {
console.log(props.product.makers)
},
}
</script>
The value is sent from another component like the one below
<ProductMakers :product="product"/>
But I always get undefined as my response. Any clue to resolve this problem? Am I missing something?
To my surprise, the template is always showing the value correctly. The problem is only inside the setup(). Any clue?
This means that product is reactively updated after the creation of component instance.
Updated value is supposed to be accessed in a watcher or a computed. In case a side effect like logging is needed, this is the case for a watcher:
setup(props) {
watchEffect(() => {
if (props.product.makers)
console.log(props.product.makers)
});
},

Paginated async Component doesn't trigger setup() on route change

I have a paginated component. The async setup() method is pulling data from an API to populate the page. It works fine when the route is directly loaded, but when I change the route to a different page slug (eg. clicking a router-link), the component is not reloaded and setup is not executed again to fetch the new data.
I guess I somehow want to force reloading the component?
This is my MainApp component it has the router view and fallback.
<router-view v-slot="{ Component }">
<Suspense>
<template #default>
<component :is="Component" />
</template>
<template #fallback>
loading...
</template>
</Suspense>
</router-view>
The router looks kinda like that. You see the page component takes a page_slug:
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "",
component: MainApp,
children: [
{
name: "page",
path: "page/:page_slug",
component: Page,
props: true,
},
// [...]
]
}
And this is how my Page component looks like. It uses the page_slug to load data from an API which is then used in the template:
<template>
<div> {{ pageData }} </div>
</template>
export default defineComponent({
name: "Page",
props: {
page_slug: {
type: String,
required: true,
},
},
async setup(props) {
const pageData = await store.dispatch("getPageData", {
page_slug: props.page_slug
});
return { pageData }
}
}
When I directly open the route, the fallback "loading..." is nicely shown until the data is returned and the component is rendered.
But when I do a route change to another page, then async setup() is not executed again. In that case the url in the browser updates, but the data just remains the same.
How can I solve this case? Do I have to force reload the component somehow? Or have an entirely different architecture to the data loading?
The answer is simple, when trying to create Vue 3 Single File Components (SFCs) in Composition API way as shown below:
<template>
<!-- Your HTML code-->
</template>
<script>
export default {
name: 'ComponentName',
async setup():{
// Your code
}
};
</script>
<style>
/*Your Style Code*/
</style>
<script>, will only executes once when the component is first imported. So, when the data have changed by other component, the component above will not updated or in other words not re-created.
To make your component re-created whenever it about to mount, you have to use <script setup> which will make sure the code inside will execute every time an instance of the component is created, but you need to re-write your script code with few changes in comparison when using setup() method, and also you are able to use both of scripts like this:
<script>
// normal <script>, executed in module scope (only once)
runSideEffectOnce()
// declare additional options
export default {
name: "ComponentName",
inheritAttrs: false,
customOptions: {}
}
</script>
<script setup>
// executed in setup() scope (for each instance)
</script>
Read this documentation carefully to have full idea.

How to listen for a page $emit in a nuxt layout?

Nuxt 2.15.6; I want to switch the layout of my menu component by dynamically switching menu components in my root layout.
default.vue
<template>
<component :is="navLayout"></component>
<Nuxt :navLayout="navLayout = $event" />
</template>
data() {
return {
navLayout: "default"
};
},
In the "child" components of , my pages eg. login.vue (/login) I $emit an event;
...
import nav2 from "#/layouts/nav2";
...
created() {
this.$emit("navLayout", nav2);
},
Now it seems to be the <Nuxt> component is not able to catch the event. I also tried calling a <Nuxt #navLayout="test()" /> method.
How can I avoid this.$root.$emit(...); in my login.vue and
this.$root.$on("navLayout", navLayout => {
this.navLayout = navLayout;
});
in default.vue?
EDIT: This answer works fine as it looks like you cannot do it right now: https://github.com/nuxt/nuxt.js/issues/8122#issuecomment-709443008
In the child component
<button #click="$nuxt.$emit('eventName', 'nice payload')">nice</button>
On the default layout
<script>
export default {
created() {
this.$nuxt.$on('eventName', ($event) => this.test($event))
},
methods: {
test(e) {
console.log('test ok >>', e)
},
},
}
</script>
Putting a listener on Nuxt itself does not work.
<Nuxt #navLayout="navLayout = $event" :navLayout="navLayout" />
I can see the event go and the listener plugged to <nuxt></nuxt> but it does not trigger any method with the listener...
PS: works for <nuxt-child></nuxt-child> at least.
Maybe I don't understand your question correctly, but it seems to me that you are trying to do yourself what layouts are meant to do. This would mean that you would create a layout with the menu component for login, a default layout etc. like this:
login.vue
<template>
<LoginMenu>
<Nuxt/>
</template>
default.vue
<template>
<DefaultMenu>
<Nuxt/>
</template>
On your page you would do:
export default {
layout: login
}
And then that would load the layout with the login menu component. On all other pages it would load the default menu.
More info here: https://nuxtjs.org/examples/layouts/

ag-grid in vue display custom component when loading the rows

Am trying to display the v-progress-circular vuetify component when ag-grid is loading the rows, i have been following the ag-grid documentation but that doesn't seems to work. the ag-grid documentation for vue seems to be outdated so i don't know what to do. What i have done so far is the following:
TableProgress.vue
<template>
<v-progress-circular :value="20" color="primary" indeterminate>
</v-progress-circular>
</template>
<script>
export default {
name: 'TableProgress'
}
</script>
MyTable.vue (Relevant parts)
<template>
<ag-grid-vue
:grid-options="gridOptions"
class="ag-theme-material"
:frameworkComponents="frameworkComponents"
:loadingOverlayComponent="loadingOverlayComponent"
/>
</template>
<script>
import TableProgress from "./TableProgress";
export default{
data(){
return{
gridOptions: null,
frameworkComponents: null,
loadingOverlayComponent: null
}
},
beforeMount () {
this.frameworkComponents = {
tableProgress: TableProgress
}
this.gridOptions.loadingOverlayComponent = 'tableProgress'
this.loadingOverlayComponent = 'tableProgress'
},
}
</script>
What am i doing wrong here? Or is it that this simply doesn't work on vue?
I think the attribute is overlayLoadingTemplate in ag-grid instead of loadingOverlayComponent. Kindly visit ag-grid overlays to see how you can add loader to your ag-grid table
You import the TableProgress but you don't use it. You must add it in components.
<script>
import TableProgress from "./TableProgress";
export default {
components: {
TableProgress
},
//...
}
</script>

How to pass data from one view to another with the vue-router

When using the vue-router with .vue files, there is no documented way to pass data from one view/component to another.
Let's take the following setup...
main.js:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
let routes = [
{
path: '/page1',
component: require('./views/Posts.vue')
},
{
path: '/page2',
component: require('./views/EditPost.vue')
}
];
let router = new VueRouter({
routes
});
new Vue({
el: '#main',
router
});
Posts.vue:
<template>
<div>
Posts.vue passing the ID to EditPost.vue: {{ postId }}
</div>
</template>
<script>
export default {
data() {
return {
allPostsHere: // Whatever...
}
}
}
</script>
EditPost.vue:
<template>
<div>
EditPost.vue received ID from Posts.vue: {{ receivedId }}
</div>
</template>
<script>
export default {
data() {
return {
receivedId: // This is where I need the ID from Posts.vue
}
}
}
</script>
Please note: It is not possible to receive the ID directly from the EditPost.vue, because it has to be selected from Posts.vue.
Question: How can I pass the ID from one view/component to the other?
A route can only be accessed via a URL and a URL has to be something user can type into the URL bar, therefore to pass a variable from one view component to another you have to use route params.
I assume you have a list of posts in Posts component and want to change page to edit a specific post in EditPost component.
The most basic setup would be to add a link in the post list to redirect to the edit page:
<div v-for="post in posts">
{{ post.title }}
<router-link :to="'/post/' + post.id + '/edit'">Edit</router-link>
</div>
Your routes would look like this:
[
{
path: '/posts',
component: require('./views/Posts.vue'),
},
{
path: '/post/:postId/edit',
component: require('./views/EditPost.vue'),
props: true,
},
]
The props configuration option is just to inform the Router to convert route params to component props. For more information see Passing props to route components.
Then in EditPost you'd accept the id and fetch the post from server.
export default {
props: ['postId'],
data() {
return {
post: null,
}
},
mounted() {
this.fetchPost();
},
methods: {
fetchPost() {
axios.get('/api/post/' + this.postId)
.then(response => this.post = response.data);
},
},
}
After the request has been completed, EditPost has its own copy which it can further process.
Note, that on every post edit and every time you enter the post list, you'll make a request to the server which in some cases may be unnecessary, because all needed information is already in the post list and doesn't change between requests. If you want to improve performance in such cases, I'd advise integrating Vuex into your app.
If you decide to do so, the components would look very similar, except instead of fetching the post to edit via an HTTP request, you'd retrieve it from the Vuex store. See Vuex documentation for more information.
if you don't want the params appear in the URL bar,you can use window.sessionStorage, window.localStorage or vuex.
Before you leave the view, set your parameters and get it after entering the new view.
You can use a prop on the <router-view :my-id="parentStoredId"></router-view> to pass down data present in the app.vue (main component). To change the parent data you need to emit a custom event comprising the value, from the childs (Posts.vue, EditPost.vue).
Another way is the Non Parent-Child Communication.
The way I prefer is Vuex. Even if it require you to learn the usage, it will repay back when the app grows.