Props passed into a a component do not render - javascript

I am passing some data values into a component but when I try to render that component, the data is not received correctly. I get an error stating:
Cannot read property 'title' of undefined at Proxy.render (eval at
./node_modules/cache-loader/dist/cjs.js?
I am sure the data I am attempting to receive is correct because after a second the data loads on the page correctly. However I need the data to load on page load because I plan on implementing an edit feature. I have done things similar to this throughout my project in different sections and it works perfectly. I am not sure why an error is being thrown here though.
I have attempted to change at what point the data is recognized, passing in concrete data values, etc. But nothing has worked.
Component:
<template>
<div class="column">
<h1 class="title">{{ this.relevantData.title }}</h1>
<div class="buttons are-large">
<a class="button is-info is-outlined" #click="journalPush()">Journal</a>
<a class="button is-success">Edit</a>
</div>
</div>
</template>
<script>
import router from "#/router.js";
export default {
props: ['relevantData'],
methods: {
journalPush() {
router.push("/recipes/" + this.relevantData.documentID + "/journal");
}
},
mounted() {
console.log(this.relevantData);
}
}
</script>
Parent:
<template>
<section class="overall">
<div class="is-mobile">
<div class="columns">
<Content :relevantData="sidebarData"/>
</div>
</div>
</section>
</template>
<script>
// # is an alias to /src
import Content from '#/components/recipes/contentComponent.vue';
import { db } from '#/main.js'
export default {
data() {
return {
sidebarData: null,
}
},
components: {
Sidebar,
},
created(){
db.collection('recipes').orderBy('dateMade', 'desc').get()
.then((snapshot) => {
this.sidebarData = snapshot.docs.map(doc => doc.data());
})
}
}
I expect when I log relevantData in the component in the mounted section, I should be able to see all the data immediately.

If you need the data to exist in the mounted hook then you'll need to use a v-if to delay the creation of the component:
<Content v-if="sidebarData" :relevantData="sidebarData"/>
Without the v-if the Content component will be created and rendered immediately, before the sidebarData has loaded from your server.

Related

How to render bootstrap-vue component that is served from a database

I have a database that has bootstrap-vue components stored and is being used in a vue component.
The data being served from the database is below.
<b-button>I am a test button</b-button>
This should be rendered and appear here in the vue component.
<div v-html="page.content"></div>
By using v-html I only see "I am a test button" when rendered and not the button itself. I have attempted to use the render method but it only outputs the above as a string and does not render an actual button.
Is it not possible to have bootstrap-vue components served dynamically from a database and then rendered? If it is, how could I go about achieving this?
This is the code I am using for the vue component below.
<template>
<b-row v-if="pageData">
<b-col lg="12" class="text-left"
v-for="page in pageData.pages"
:key="page.pageID">
<h1 v-html="page.title"></h1>
<sub v-html="page.subtitle"></sub>
<div v-if="page.heroImage">
<b-img-lazy :src="showImage(page.heroImage)" right thumbnail></b-img-lazy>
</div>
<div v-html="page.content"></div>
</b-col>
</b-row>
</template>
<script>
import { getData } from './mixins/getData';
export default {
name: 'ContentComponent',
mixins: [getData],
data: function() {
return {
pageData: null,
}
},
methods : {
showImage(image) {
if(image) {
return this.image = require('../assets/img/'+ image)
}
},
created() {
this._getData(this.$route.name,'pages');
}
}
</script>

How to display an element in Vue component only after NProgress.done()

For displaying the loading status in a VueJS application I use the library NProgress. It works well and shows the loading bar and the spinning wheel. However the HTML content of the page is already rendered and displayed. I'd like to hide certain parts of the page while the request is running.
Is there any possibility to check programmatically for NProgress.done() and display the contents after it has been called?
I'd like something like this:
<template>
<div>
<NavBar />
<div v-show="check here for NProgress.done()">
<p>Here are all the nice things with placeholders for the data from the API.</p>
</div>
</div>
</template>
<script>
import NavBar from '#/components/NavBar';
export default {
components: {
NavBar
}
}
</script>
The part "check here for NProgress.done()" is what I don't know how to solve.
Looking through the documentation of NProgress, it looks like it exposes a ".status", which returns the value of the current loader, but returns a "null" when the loader isn't "started".
<template>
<div>
<div v-show="state.status == null">
<p>
Here are all the nice things with placeholders for the data from the
API.
</p>
</div>
</div>
</template>
<script>
import Vue from "vue";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
const state = Vue.observable(NProgress);
export default {
data: () => ({ state }),
mounted: function () {
NProgress.start(); // Start the bar loading
setTimeout(() => { // Perform your async workloads
NProgress.done(); // .done() to finish the loading
}, 5000);
},
};
</script>
You'd need to make NProgress reactive, so you can just use Vue.observable(state).

Pass the clicked element's data by using eventbus in Vue.js

Im pretty new to Vue.js, thank you for your understanding. Im setting up a Vue project where I want to show Patients and their data. I want to tell from the beginning that Im not planning to use Vuex :)
My project has 3 layers.
Home.vue file where I import the data (patients)
Next layer is Patients.vue component where I have a for loop and output all the patients. In this case, I am getting the patient Array by using props.
And the last layer is called ViewPatient.vue view. What I want to do here is showing more details of the clicked Patient. I want to inherit for example the name to make one more call to the endpoint to retrieve some observations of the patient. For example: endpoint/patient/(theName) <-- the name should come from the previous Patients.vue component.
I tried a lot of different approaches: eventbus, dynamic router and data-attrbutes.
Home.vue
<template>
<div class="container">
<keep-alive>
<Patients :PatientsData="PatientsData" />
</keep-alive>
</div>
</template>
<script>
// # is an alias to /src
import PatientsData from "../data/messages";
import Patients from "../components/Patients.vue";
export default {
name: "home",
data() {
return {
PatientsData: PatientsData
};
},
components: {
Patients
}
};
</script>
Patients.vue (component)
<template>
<div v-if="PatientsData.length > 0">
<div class="row row-eq-height">
<div v-for="PatientData in PatientsData" class="col-12 col-sm-6 col-md-3 mb-3" :key="PatientData.content" :data-id="PatientData.content" #click.prevent="passPatientData" >
<router-link to="/patient" >
<div class="col-12 patientsTiles p-4">
<p class="patientsName">
<span>Navn</span>
{{ PatientData.content }}
</p>
<p class="patientsCPR">
<span>CPR.nr</span>
{{ PatientData.subject }}
</p>
<p class="patientsAge">
<span>Alder</span>
{{PatientData.age}}
</p>
<i :class="['fa', 'fa-star', {important: PatientData.isImportant}]"></i>
</div>
</router-link>
</div>
</div>
</div>
</template>
<script>
import router from "../main";
import { eventBus } from "../main";
export default {
props: {
PatientsData: Array,
},
data(){
return{
patientName: ""
}
},
methods: {
passPatientData() {
this.patientName = this.PatientData;
alert(this.patientName);
eventBus.$emit("passPatientData", this.patientName);
}
}
};
</script>
ViewPatient.vue (view)
<template>
<div class="container">
<h1>The Patient detail</h1>
</div>
</template>
<script>
// # is an alias to /src
import { eventBus } from "../main";
export default {
props: {
// patientId:{
// type: String
// }
},
data() {
return {
selectedPatient : ""
};
},
created() {
eventBus.$on("passPatientData", data => {
this.selectedPatient = data;
// console.log("yeaah");
})}
}
</script>
IMO, the problem is lying on the passPatientData function.
this.PatientData is empty and I dont know how to pass the clicked element's data to the empty string (this.patientName), so I can emit it to the eventbus
passPatientData() {
this.patientName = this.PatientData;
alert(this.patientName);
eventBus.$emit("passPatientData", this.patientName);
}
Here is my approach which worked for me (few changes):
#bbsimonbb, thank you for the answer, but I´m not going to use that approach, because its a bit overkill when compared to my small task.
In Patients.vue while looping patients, I have modified the click event:
Im actually passing the single element that is being clicked, which solved med a lot of time.
<div v-for="PatientData in storedPatients" class="col-12 col-sm-6 col-md-3 mb-3" :data-id="PatientData.content" #click="passPatientData(PatientData)" >
Before:
#click="passPatientData"
After:
#click="passPatientData(PatientData)"
And then in my event bus im able to "work" with the data im passing:
methods: {
passPatientData(element) {
this.patientName = element.content;
alert(this.patientName);
eventBus.$emit("passPatientData", this.patientName);
}
}
The purpose is to pass the patientName to ViewPatient.vue file by using eventbus and call a new endpoint which looks like this: endpoint/patient/(patientName) . The result of the endpoint will then be the details of the single patient that has been clicked in patients.vue
It´s working. Hope it can be useful for others that is struggling with the same issue.
You've decided not to use Vuex. That doesn't mean you shouldn't use a store. The simplest store is just an object in the data of your root Vue, passed around via provide/inject. Put your shared data in there.
This is a much simpler pattern to get your head around than an event bus. You get the shared state right out of Vue components, and because it's just an object, that does nothing but store state, you will be more in control of what you have and how it's working.
// Home.vue
export default {
name: "home",
provide: { mySharedData: this.mySharedData },
data() {
return {
mySharedData: {
patientData: {}
}
};
},
created() {
fetch("http://patientData")
.then(response.json)
.then((patientData) => this.mySharedData.patientData = patientData)
}
...
// Then, in all other components of your app...
export default {
inject: ['mysharedData'],
...
}
This way, you'll be making full use of Vue reactivity to propagate your changes. You'll need to understand how Vue makes properties reactive. In particular, you can't just assign new props in mySharedData. The simplest way is to make sure all the props are there to start with. To do it on the fly, use Vue.set().

Change a property's value in one component from within another component

I'm trying to wrap my head around hoe Vue.js works, reading lots of documents and tutorials and taking some pluralsight classes. I have a very basic website UI up and running. Here's the App.vue (which I'm using kinda as a master page).
(To make reading this easier and faster, look for this comment: This is the part you should pay attention to)...
<template>
<div id="app">
<div>
<div>
<CommandBar />
</div>
<div>
<Navigation />
</div>
</div>
<div id="lowerContent">
<!-- This is the part you should pay attention to -->
<template v-if="showLeftContent">
<div id="leftPane">
<div id="leftContent">
<router-view name="LeftSideBar"></router-view>
</div>
</div>
</template>
<!-- // This is the part you should pay attention to -->
<div id="mainPane">
<div id="mainContent">
<router-view name="MainContent"></router-view>
</div>
</div>
</div>
</div>
</template>
And then in the same App.vue file, here's the script portion
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import CommandBar from './components/CommandBar.vue';
import Navigation from './components/Navigation.vue';
#Component({
components: {
CommandBar,
Navigation,
}
})
export default class App extends Vue {
data() {
return {
showLeftContent: true // <--- This is the part you should pay attention to
}
}
}
</script>
Ok, so the idea is, one some pages I want to show a left sidebar, but on other pages I don't. That's why that div is wrapped in <template v-if="showLeftContent">.
Then with the named <router-view>'s I can control which components get loaded into them in the `router\index.ts\ file. The routes look like this:
{
path: '/home',
name: 'Home',
components: {
default: Home,
MainContent: Home, // load the Home compliment the main content
LeftSideBar: UserSearch // load the UserSearch component in the left side bar area
}
},
So far so good! But here's the kicker. Some pages won't have a left side bar, and on those pages, I want to change showLeftContent from true to false. That's the part I can't figure out.
Let's say we have a "Notes" component that looks like this.
<template>
<div class="notes">
Notes
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
data() {
return {
showLeftContent: false // DOES NOT WORK
}
}
}
</script>
Obviously, I'm not handling showLeftContent properly here. It would seem as if the properties in data are scoped only to that component, which I understand. I'm just not finding anything on how I can set a data property in the App component and then change it in a child component when that child is loaded through a router-view.
Thanks!
EDIT:
I changed the script section of the Notes component from:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
data() {
return {
showLeftContent: false // DOES NOT WORK
}
}
}
</script>
to:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
mounted() {
this.$root.$data.showLeftContent = false;
}
}
</script>
And while that didn't cause any compile or runtime errors, it also didn't have the desired effect. On Notes, the left side bar still shows.
EDIT 2:
If I put an alert in the script section of the Notes component:
export default class Notes extends Vue {
mounted() {
alert(this.$root.$data.showLeftContent);
//this.$root.$data.showLeftContent = false;
}
}
The alert does not pop until I click on "Notes" in the navigation. But, the value is "undefined".
EDIT 3:
Struggling with the syntax here (keep in mind this is TypeScript, which I don't know very well!!)
Edit 4:
Inching along!
export default class App extends Vue {
data() {
return {
showLeftContent: true
}
}
leftContent(value: boolean) {
alert('clicked');
this.$root.$emit('left-content', value);
}
}
This does not result in any errors, but it also doesn't work. The event never gets fired. I'm going to try putting it in the Navigation component and see if that works.
As it says on #lukebearden answer you can use the emit event to pass true/false to the main App component on router-link click.
Assuming your Navigation component looks like below, you can do something like that:
#Navigation.vue
<template>
<div>
<router-link to="/home" #click.native="leftContent(true)">Home</router-link> -
<router-link to="/notes" #click.native="leftContent(false)">Notes</router-link>
</div>
</template>
<script>
export default {
methods: {
leftContent(value) {
this.$emit('left-content', value)
}
}
}
</script>
And in your main App you listen the emit on Navigation:
<template>
<div id="app">
<div>
<Navigation #left-content="leftContent" />
</div>
<div id="lowerContent">
<template v-if="showLeftContent">
//...
</template>
<div id="mainPane">
//...
</div>
</div>
</div>
</template>
<script>
//...
data() {
return {
showLeftContent: true
}
},
methods: {
leftContent(value) {
this.showLeftContent = value
}
}
};
</script>
A basic approach in a parent-child component relationship is to emit events from the child and then listen and handle that event in the parent component.
However, I'm not sure that approach works when working with the router-view. This person solved it by watching the $route attribute for changes. https://forum.vuejs.org/t/emitting-events-from-vue-router/10136/6
You might also want to look into creating a simple event bus using a vue instance, or using vuex.
If you'd like to access the data property (or props, options etc) of the root instance, you can use this.$root.$data. (Check Vue Guide: Handling Edge)
For your codes, you can change this.$root.$data.showLeftContent to true/false in the hook=mounted of other Components, then when Vue creates instances for those components, it will show/hide the left side panel relevantly.
Below is one demo:
Vue.config.productionTip = false
Vue.component('child', {
template: `<div :style="{'background-color':color}" style="padding: 10px">
Reach to root: <button #click="changeRootData()">Click me!</button>
<hr>
<slot></slot>
</div>`,
props: ['color'],
methods: {
changeRootData() {
this.$root.$data.testValue += ' :) '
}
}
})
new Vue({
el: '#app',
data() {
return {
testValue: 'Puss In Boots'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<h2>{{testValue}}</h2>
<child color="red"><child color="gray"><child color="green"></child></child></child>
</div>

Ho to properly pass a method in a child component in Vue.js?

I have a child component called goback.vue which only function is to go back one step in the navigation history. The reason why I am asking this question is because I am trying to reuse this component often in many other components and I want to be able to edit the style and template later on just one place.
This is the code of the component goback.vue:
<template>
<span #click="backOneStep">
<svg type="submit" class="arrowleft" >
<use href="../static/icons/iconsset.svg#arrowleft"></use>
</svg>
</span>
</template>
<script>
export default {
name: 'goback',
data () {
return {
}
},
methods: {
backOneStep(){
window.history.go(-1)
},
}
}
<script>
Then on my several of my parent components I am importing the child component in the following manner:
<template>
<div class="building">
<div id="title">
<goback></goback> // using the child component here
</div>
</div>
</template>
<script>
import goback from './goback.vue';
export default {
components: {
goback
},
name: 'maincomponent',
data () {
return {
}
}
},
methods: {
backOneStep(){ // do I need to repeat this here?
window.history.go(-1)
},
}
}
</script>
First, I am wondering if I need to repeat the method on all parent components or if I can just write it on my child component.
Second, I am getting an error message:
[Vue warn]: Property or method "backOneStep" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
<Goback> at src/components/goback.vue
<Depts> at src/components/depts.vue
<App> at src/App.vue
<Root>
Note: I have carefully read the link but I am still stuck
Immediately following the previous error message I am also getting this error message:
[Vue warn]: Invalid handler for event "click": got undefined
found in
<Goback> at src/components/goback.vue
<Depts> at src/components/depts.vue
<App> at src/App.vue
<Root>
Would be able to tell me what I can do to avoid this?
Is this related to props? I have tried declaring the "backOneStep" prop in the data of the goback.vue but I am not sure I have done it correctly. What am I missing here?
You can use a $emit event to tell the parent to "go back" on click in the child component:
<template>
<span #click="backOneStep">
<svg type="submit" class="arrowleft" >
<use href="../static/icons/iconsset.svg#arrowleft"></use>
</svg>
</span>
</template>
<script>
export default {
name: 'goback',
methods: {
backOneStep() {
this.$emit('back')
}
}
}
</script>
And in the parent:
<template>
<div class="building">
<div id="title">
<goback #back="window.history.go(-1)"></goback>
</div>
</div>
</template>
This did it for me as the main reason I wanted to isolate this element/function on a component was to be able to change the style of the element in one place:
The child component:
<template>
<span v-on:click="$emit('back')">
<svg type="submit" class="arrowleft" >
<use href="../static/icons/iconsset.svg#arrowleft"></use>
</svg>
</span>
</template>
<script>
export default {
name: 'goback',
data () {
return {
}
}
}
<script>
<style scoped>
// enter styles here
</style>
On the parent:
<template>
<div class="building">
<div id="title">
<goback #back="window.history.go(-1)"></goback>
</div>
</div>
</template>
<script>
import goback from './goback.vue';
export default {
components: {
goback
},
methods: {
backOneStep(){
window.history.go(-1)
},
}
}
</script>

Categories

Resources