(Nuxt) Vue component doesn't show up until page refresh - javascript

I'm storing nav items in my Vuex store and iterating over them for conditional output, in the form of a Vue/Bulma component, as follows:
<b-navbar-item
v-for='(obj, token) in $store.state.nav'
v-if='privatePage'
class=nav-link
tag=NuxtLink
:to=token
:key=token
>
{{obj.text}}
</b-navbar-item>
As shown, it should be output only if the component's privatePage data item resolves to true, which it does:
export default {
data: ctx => ({
privatePage: ctx.$store.state.privateRoutes.includes(ctx.$route.name)
})
}
The problem I have is when I run the dev server (with ssr: false) the component doesn't show up initially when I navigate to the page via a NuxtLink tag. If I navigate to the page manually, or refresh it, the component shows.
I've seen this before in Nuxt and am not sure what causes it. Does anyone know?

recommendation :
use mapState and other vuex mapping helper to have more readable code :).
dont use v-for and v-if at the same element
use "nuxt-link" for your tag
use / for to (if your addresses dont have trailing slash)
<template v-if='privatePage'>
<b-navbar-item
v-for='(obj, token) in nav'
class=nav-link
tag="nuxt-link"
:to="token" Or "`/${token}`"
:key="token"
>
{{obj.text}}
</b-navbar-item>
</template>
and in your script :
<script>
import {mapState} from 'vuex'
export default{
data(){
return {
privatePage: false
}
},
computed:{
...mapState(['privateRoutes','nav'])
},
mounted(){
// it's better to use name as a query or params to the $route
this.privatePage = this.privateRoutes.includes(this.$route.name)
}
}
</script>
and finally if it couldn't have help you , I suggest to inspect your page via dev tools and see what is the rendered component in html. it should be an <a> tag with href property. In addition, I think you can add the link address (that work with refresh and not by nuxt link) to your question, because maybe the created href is not true in navbar-item.
NOTE: token is index of nav array . so your url with be for example yourSite.com/1.so it's what you want?

This question has been answered here: https://stackoverflow.com/a/72500720/12747502
In addition, the solution to my problem was a commented part of my HTML that was outside the wrapper div.
Example:
<template>
<!-- <div>THIS CREATES THE PROBLEM</div> -->
<div id='wrapper'> main content here </div>
</template>
Correct way:
<template>
<div id='wrapper'>
<!-- <div>THIS CREATES THE PROBLEM</div> -->
main content here
</div>
</template>

Related

How to use a querySelector in Nuxt 3?

I'm trying to initialise IntersectionObserver in each page of my website built with Nuxt3.
Therefore, I want to access each HTML element that has a specific CSS class. However, on page change, I noticed that via onMounted hook the detected elements are from the previous page.
Here a easy to reproduce example:
app.vue
<template>
<div>
<NuxtPage />
</div>
</template>
pages/index.vue
<script setup lang="ts">
onMounted(() => {
console.group("index.vue");
console.log(document.querySelector("#container"));
console.groupEnd();
});
</script>
<template>
<div id="container">
<h1>INDEX</h1>
<NuxtLink to="/work">
Go to work
</NuxtLink>
</div>
</template>
pages/work.vue
<script setup lang="ts">
onMounted(() => {
console.group("work.vue");
console.log(document.querySelector("#container"));
console.groupEnd();
});
</script>
<template>
<div id="container">
<h1>WORK</h1>
<NuxtLink to="/">
Go to index
</NuxtLink>
</div>
</template>
Simply, the result in the console always come from the previous DOM. Here the steps:
Load the page on index.vue, you see the right element in the console.
Go to work.vue using the link.
See the console showing the exact same result as previously, yet with an added empty class attribute on #container
My question is, why does onMounted hook doesn't show the right DOM on page change?
I tried to set the page transition to the default mode:
definePageMeta({
pageTransition: {
mode: 'default',
},
});
Nothing changed.
NOTE: I am using nuxt version: 3.0.0-rc.9
For this kind of usage, you should use template refs: https://vuejs.org/guide/essentials/template-refs.html#accessing-the-refs
Otherwise, Vue will not behave as expected. More details can be found in this other answer.

Vue template isn't rendering in for loop

So after following a beginner Vue tutorial to setup a Todo app, I decided to try to adapt some parts of it for a website I'm trying to make. What I'm stuck on is that despite everything saying my for-loop is supposed to work, it doesn't.
The project itself was created using the vue-cli, and most of the code copy-pasted from the tutorial. (which is working fine with its own for-loop)
It seems like the data might be not passed onto the template maybe?
I have tried:
having the info inside the props and data sections
passing whole object and only parameters to the template
tried with hard-coded values inside array which is iterated on
(After setting up a new vue-cli project:)
App.vue:
<template>
<div id="app">
<create-section v-on:create-section="addSection" />
<section v-for="section in sections" v-bind:key="section.title" :info="section"></section>
</div>
</template>
<script>
import CreateSection from "./components/CreateSection";
import Section from "./components/Section";
export default {
name: "App",
components: {
CreateSection,
Section
},
data() {
return {
sections: []
};
},
methods: {
addSection(section) {
this.sections.push({
title: section.title,
description: section.description
});
console.log(
"Added to sections! : " + section.title + " | " + section.description
);
console.log("Sections length: " + this.sections.length);
}
}
};
</script>
Section.vue
<template>
<div class="ui centered card">
<div class="content">
<div class="header">{{ info.title }}</div>
<div>{{ info.description }}</div>
</div>
</div>
</template>
<script type = "text/javascript" >
export default {
props: {info: Object},
data() {
return {};
}
};
</script>
Expected result:
Display Section template on the website (after creating it with addSection that another script calls. Not included for brevity)
Actual result:
Nothing is displayed, only a empty tag is added
I believe the problem is that you've called it Section. As <section> is a standard HTML element you can't use it as a component name.
There is a warning built into the library but it seems to be case sensitive, which isn't entirely helpful. Try changing your components section to this:
components: {
CreateSection,
section: Section
},
You should then see the warning.
The fix would just be to call it something else.
This is mentioned in the first entry in the style guide:
https://v2.vuejs.org/v2/style-guide/#Multi-word-component-names-essential
section is an existing HTML5 element, you should name your section component something different.
If you really want to name the component Section, register it as 'v-section'
The problem is that when you do the loop in the <section v-for="section in sections" v-bind:key="section.title" :info="section"></section> the Array sections is not ready, there is nothing there.. so when you add new things to this array you need to trigger (computed prop) to send again the data to the section component.
Aside from the issue with using an existing HTML5 command as a name for your Vue component (you should change that to another name by the way), you should also look into how you declared the props within Section.vue. The code below shows the correct way to do it:
<script type = "text/javascript" >
export default {
props: ['info'],
data() {
return {};
}
};
</script>
The props take in the name of the property being declared from the parent component and should be a string.
Hope this helps.

How to use an image as a button in Vue.js?

I'm trying to make a login button as a single-file-component in Vue.js (it's a Rails app with a Vue.js front-end). If you click this button, it's supposed to take you to the an external provider's login page.
How can I use an image as a button? I'm guessing you use v-on:click for the actual redirect, but I'm stuck there.
Right now, this code below shows a hardcoded button that looks like img(src="../assets/img/login_button.png"). You can click on it, but that's obviously not what I want. I want to show the actual png image, not the path.
// LoginButton.vue
<template lang="pug">
#login-button
<button v-on:click="redirect_to_login">img(src="../assets/img/login_button.png")</button>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
#Component
export default class LoginButton extends Vue{
redirect_to_login():void{ // I haven't written this method yet
}
}
</script>
Is there any reason you can't just use normal HTML image inside your button? I haven't used pug before.
<button v-on:click="redirect_to_login"><img src="../assets/img/login_button.png" /></button
Though since you're using Vue and not an actual HTML form you might not even need a button you could just add the click binding to the image instead
<img src="../assets/img/login_button.png" v-on:click="redirect_to_login" />
I am not familiar with pug, so I don't know what the correct syntax you'll need is. But you can use the <router-link> tag to set the route. For example (using Vuetify)
<router-link to="/">
<v-img src="/path/to/img.gif"/>
</router-link>
Either you can use:
<a #click="Redirect">
<img src='IMAGE_SRC' />
</a>
or
<img #click="Redirect" src='IMAGE_SRC'/>
new Vue({
el: '#app',
methods:
{
Redirect()
{
window.location.href = "https://jsfiddle.net/";
//or
//this.$router.push('LINK_HERE'); // if ur using router
}
}
})
Demo LINK:
https://jsfiddle.net/snxohqa3/5/

Include component from parent app in component contained in node_modules directory

I am working on Vue app that incorporates Vue Bootstrap Calendar, and I would like to be able to override the content of the day cell (handled by the Day.vue component) to add my own custom content inside. My thought was initially to modify the Day component to include <slot></slot> tags and pass in the custom content that way.
The problem has to do with accessing the Day component. To include the calendar in your app, you include the Calendar.vue component, which includes Week.vue, which in turn includes Day.vue. As I understand slots, I have to have the child component (Day.vue in this case) included in the component where I'm passing the data, which means it would need to be included in my own component.
If this is not possible, my other thought is to perhaps modify the library by adding another configuration prop (something like dayCustomContent) to the Calendar.vue that indicates that the Day cell content is custom content, pass that in to Calendar.vue, and then down to Day.vue, and then in the Day.vue template, have a v-if conditional based on this prop that either displays the custom content or the default cell content, something like:
<template>
<div class="day-cell" v-if="dayCustomContent">
...my custom content here...
</div>
<div class="day-cell" v-else>
...default events from my.events goes here...
</div>
</template>
I would probably then need to define a custom component to render whatever custom content I want to display, and somehow include that component within Day.vue.
So to sum up, my questions are these:
1) Is there a way to do what I need with slots?
2) For my second option, am I going down the right path? I'm open to suggestions.
UPDATE: I was able to get this done by adding a boolean customDayContent prop in Calendar.vue like so and passing it down to Week.vue and then to Day.vue:
<template>
...
<div class="dates" ref="dates">
<Week
v-for="(week, index) in Weeks"
:firstDay="firstDay"
:key="week + index"
:week="week"
:canAddEvent="canAddEvent"
:canDeleteEvent="canDeleteEvent"
:customDayContent="customDayContent"
:displayWeekNumber="displayWeekNumber"
#eventAdded="eventAdded"
#eventDeleted="eventDeleted"
></Week>
</div>
...
</template>
<script>
export default {
...
props: {
...
customDayContent: {
type: Boolean,
default: false
}
},
}
</script>
and then in Day.vue, do like I had suggested with v-if:
<template>
<div class="day-cell" v-if="customDayContent">
<custom-day></custom-day>
</div>
<div
class="day-cell"
:class="{'today' : day.isToday, 'current-month' : day.isCurrentMonth, 'weekend': day.isWeekEnd, 'selected-day':isDaySelected}"
#click="showDayOptions"
v-else
>
... existing code goes here...
</div>
</template>
The last part is referencing the CustomDay.vue component referenced in my v-if block. I want the user to be able to define the content of their own custom CustomDay.vue template in their own parent app. However, I'm having trouble trying to figure out how to do that. Following the pattern of including components already in this component, I added this in the components section of Day.vue:
CustomDay: require("../../../../src/Components/CustomDay.vue").default
? require("../../../../src/Components/CustomDay.vue").default
: require("../../../../src/Components/CustomDay.vue")
However, no matter what I try along these lines, I get an error that the relative module was not found. On top of that, I need to add it to the componentsarray only if customDayContent is true. What is the best way to do that? In a watcher or computer property, perhaps? Or another way?

Using props to set different text on every page in vue.js

I am using laravel + vue. I want to do title of page in navbar, so when you are in index, in navbar is text index. When you are in settings, navbar says settings etc.
I think props is good for it, but when I use that, it works not good. Look here:
blade.php of index:
#extends('layout.app')
#section('content')
<index :msg="msg"></index>
#endsection
index.vue:
props: [
'msg'
],
Now navbar:
<template>
<nav class="navbar">
<a></a>
<p>{{msg}}</p>
</nav>
</template>
<script>
export default {
props: [
],
data() {
return {
}
},
}
</script>
and layout:
<body>
<div id="app">
<navbar></navbar>
#yield('content')
</div>
</body>
How I can change that {{msg}} paragraph in navbar when we are on different pages? My code doesn't work.
[Vue warn]: Property or method "msg" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
If you want to use a prop then you need to define it in the props object of your component. In your NavBar you are referencing to msg, but the props object in NavBar is empty. Define it and pass the prop in your layout.blade.php.
Or you could also define a computed property where you take a look at the current route and return a string fitting your business.
https://v2.vuejs.org/v2/guide/computed.html
If you want to share data between multiple components, then use a store (VUEX) as proposed :)

Categories

Resources