Props are not rendering inside component - javascript

I have a simple setup: I have a parent component that passes data using props to a child component. However, when I pass the props to the child component, the data is not filled in. All the tutorials and guides I've followed use the same syntax as I'm using, so I must be missing something. I just can't seem to figure out what.
// LoginPage.vue
<script>
import Card from '../generic/Card.vue';
import Input from '../generic/Input.vue';
export default {
components: {
Card,
Input,
}
}
</script>
<template>
<Card>
<template v-slot:title>Sign in</template>
<template v-slot:content>
<Input :type="text" :placeholder="Email"></Input>
</template>
</Card>
</template>
// generic/Card.vue
<template>
<div class="card rounded-md p-8 border-2 border-sgray hover:border-sgreen text-center">
<div class="text-xl font-medium text-black">
<slot name="title"></slot>
</div>
<slot name="content"></slot>
</div>
</template>
// generic/Input.vue
<script>
export default {
props: {
type: String,
placeholder: String,
value: String,
}
}
</script>
<template>
<input type="{{ type }}" placeholder="{{ placeholder }}" value="{{ value }}"/>
</template>
When I take a look at the code in the browser, I get this:
<div id="app" data-v-app="">
<div class="flex h-screen">
<div class="m-auto">
<div class="card rounded-md p-8 border-2 border-sgray hover:border-sgreen text-center">
<div class="text-xl font-medium text-black">Sign in</div>
<input type="{{ type }}" placeholder="{{ placeholder }}">
</div>
</div>
</div>
</div>
This line is the problem:
<input type="{{ type }}" placeholder="{{ placeholder }}">
The data does not get filled in where I want them to, but it just returns {{ type }} or {{ placeholder }}, without actually filling in the passed props.
What am I doing wrong?

Mustache {{ }} is actually used for display purposes.
If we want to bind any variable inside an HTML attribute, we should use the v-bind directive.
<input v-bind:type="type" v-bind:placeholder="placeholder">
// OR
<input :type="type" :placeholder="placeholder">
A small tip-
We should avoid using the reserved keywords as variable names like type, define, and many more. It can create confusion.
What you did is also fine but it's better to use the props like this-
props: {
type: {
type: String,
// add any other validation
},
placeholder: {
type: String,
// add any other validation
}
}

Try to bind props in component:
<input :type="type" :placeholder="placeholder" :value="value"/>

Related

VueJS 3 custom Checkbox not changing UI when clicked

I'm trying to create a custom checkbox with Vue 3 and the composition API following this example, but even when I can see on devtools that all my props and bound data are passing from the parent component to the child component the checkbox UI won't change when the checkbox is checked:
Parent Component:
<template>
<div class="flex">
<div class="flex">
<span>Detected Language</span>
<BaseCheckBox
v-model:checked="translationOn"
fieldId="translate"
label="Translate"
class="ml-4"
/>
</div>
</div>
</template>
<script>
import BaseCheckBox from './BaseCheckBox.vue'
import { ref } from 'vue'
export default {
setup() {
const translationOn = ref(false)
return {
translationOn,
}
},
components: { BaseCheckBox },
}
</script>
Child Component:
<template>
<div class="flex">
<input
#input="(event) => $emit('update:checked', event.target.checked)"
type="checkbox"
:checked="checked"
:id="fieldId"
class="mr-2 hidden"
/>
<label
:for="fieldId"
class="flex flex-row items-center cursor-pointer select-none"
>
<i
class="fa mr-1"
:class="{
'fa-check-square text-blue-600': checked,
'fa-square border-2 border-gray-700 text-white': !checked,
}"
></i>
{{ label }}
</label>
</div>
</template>
<script>
export default {
props: {
label: String,
fieldId: {
type: String,
required: true,
},
checked: {
type: Boolean,
},
},
}
</script>
Whenever I click the checkbox I can see that the "translationOn" property on the parent change its value and the "checked" prop on the children change its value too but the font-awesome classes that are supposed to switch depending on that value don't:
<i
class="fa mr-1"
:class="{
'fa-check-square text-blue-600': checked,
'fa-square border-2 border-gray-700 text-white': !checked,
}"
></i>
The strange thing (at least for me) is that when I manually change the value in the code in this line of the parent component:
const translationOn = ref(true)
From "true" to "false" or viceversa it works but not when I click on the checkbox, even when I can see all the values behaving accordingly.
Will really appreciate any help! Thanks!
So found the answer to this problem here
For some reason the font-awesome classes are not reactive hence ignore the vue directive to conditional render the html. Find the answer (basically wrap the <i> tag within a <span> tag) on the link.

Vue.js 3 - inserting component into slot

What I'm trying to achieve
I'm trying to pass Component into the slot.
The question/information
How do I pass the Component into the slot so that It will be rendered? This works fine as long as I pass strings / plain html.
Additional question
If this is not possible - then how can I pass component into other component with the structure like below?
Parent
Template code
<template>
<card-with-title card-title="Title">
<template #card-body>
<row-fontawesome-icon-with-text v-for="mailDto in lastProcessedEmails"/>
</template>
</card-with-title>
</template>
Script code - the important part
<script>
import SymfonyRoutes from '../../../../../core/symfony/SymfonyRoutes';
import GetLastProcessedEmailsResponseDto from '../../../../../core/dto/api/internal/GetLastProcessedEmailsResponseDto';
import MailDto from '../../../../../core/dto/modules/mailing/MailDto';
import CardWithTitleComponent from '../../../../base-layout/components/cards/card-with-title';
import RowFontawesomeIconWithTextComponent from '../../../../other/row-fontawesome-icon-with-text';
export default {
components: {
'card-with-title' : CardWithTitleComponent,
'row-fontawesome-icon-with-text' : RowFontawesomeIconWithTextComponent,
},
<...>
Child
<!-- Template -->
<template>
<div class="col-12 col-lg-4 mb-4">
<div class="card border-light shadow-sm">
<div class="card-header border-bottom border-light">
<h2 class="h5 mb-0">{{ cardTitle }}</h2>
</div>
<div class="card-body">
<slot name="card-body"></slot>
<slot></slot>
</div>
</div>
</div>
</template>
<!-- Script -->
<script>
export default {
props: [
"cardBody",
"cardStyle",
"cardTitle"
],
}
</script>
I did research about the question, I've seen in documentation how does the named slots work like, but non of the posts / blogs entries answer / solve my Problem.
Examples of checked resources:
https://www.smashingmagazine.com/2019/07/using-slots-vue-js/
How to insert named slots into parent components
https://medium.com/js-dojo/vue-named-slot-shorthand-8a920358e861
https://v3.vuejs.org/guide/component-slots.html
https://medium.com/#norton.seanm/vue-js-slots-8a274c80450e
I've found the solution... It's pretty much... terrifying.
Vue is not checking if the array is empty, on the v-for it tries to loop over and then throws error.
Personally, from other languages / frameworks - this should not happen.
But well, this is the solution:
<!-- Template -->
<template>
<card-with-title card-title="Title">
<template #card-body>
<div v-if="[lastProcessedEmails.length]">
<row-fontawesome-icon-with-text v-for="mailDto in lastProcessedEmails">
<template #icon>
<i class="font-weight-bold">
<i v-if="mailDto.status === mailStatusSent" :class="fontawesomeIconClassesSent"></i>
<i v-else-if="mailDto.status === mailStatusPending" :class="fontawesomeIconClassesPending"></i>
<i v-else :class="fontawesomeIconClassesError"></i>
</i>
</template>
<template #title>
{{ mailDto.subject }}
</template>
<template #title-context>
{{ mailDto.created }}
</template>
</row-fontawesome-icon-with-text>
</div>
</template>
</card-with-title>
</template>
The whole problem was that:
data(){
return {
lastProcessedEmails: [],
}
},
The lastProcessedEmails is updated via Axios Call.

Rendering VUE slot only if content matches specific value

I have two routes that render the same component but with different data coming from an API.
This component has a child component called <base-section> that has a v-if directive that checks if a specific slot has content or not (because if it has no content, I don't want the slot to show).
However, there might be more than one instance of this child component on the same parent component, and therefore, if one of the instances has content in the slot, but the other one doesn't, VUE will automatically assume that all slots have content.
Therefore, I would like to know if there is any way of checking the specific slot content of each instance and then compare it with the data coming from the API. Please find my code below:
Parent component (Content.vue)
<template>
<base-section
v-for="entry in this.entries"
:key="entry.title"
lang="lang-markup"
:title="entry.title"
>
<template v-slot:content>
<span v-html="entry.content"></span>
</template>
<template v-slot:examples>
<div v-html="entry.examples"></div>
</template>
<template v-slot:code>
{{ entry.code }}
</template>
</base-section>
</template>
Child component (BaseSection.vue)
<template>
<hr class="my-6" />
<h4 class="text-salmon">{{ title }}</h4>
<section>
<div class="section-sm txt-justify" v-if="this.$slots.content">
<slot name="content"></slot>
</div>
<span class="medal bg-light text-dark code-medal">Examples</span>
<div class="section-sm border-light-1 mb-3">
<slot name="examples"></slot>
</div>
<span class="medal text-dark code-medal">Code</span>
<pre :class="lang + ' border-light-1 bg-light'">
<code><slot name="code"></slot></code>
</pre>
</section>
</template>
The data coming from the API follows this structure:
getData() {
const url = this.apiUrl + this.$route.name + this.apiToken
fetch(url)
.then((collection) => collection.json())
.then((collection) => {
const entries = [];
this.entries = [];
for (const id in collection.entries) {
if (collection.entries[id].Version === this.byteVersion) {
entries.push({
title: collection.entries[id].Title,
content: collection.entries[id].Content,
examples: collection.entries[id].Examples,
code: collection.entries[id].Code,
});
}
}
this.entries = entries;
});
}
Thank you very much for your help!
Regards,
T.
Maybe you can pass the "entry.content" into your BaseSection component. and v-if the entryContent.
Parent component (Content.vue)
<template>
<base-section
v-for="entry in this.entries"
:key="entry.title"
lang="lang-markup"
:title="entry.title"
:entryContent="entry.content"
>
<template v-slot:content>
<span v-html="entry.content"></span>
</template>
<template v-slot:examples>
<div v-html="entry.examples"></div>
</template>
<template v-slot:code>
{{ entry.code }}
</template>
</base-section>
</template>
Child component (BaseSection.vue)
<div class="section-sm txt-justify" v-if="entryContent">
<slot name="content"></slot>
</div>
Or you can v-if your content template
<template v-slot:content v-if="entry.content">
<span v-html="entry.content"></span>
</template>

Vue.js Property or method "options" is not defined on the instance but referenced during render

I working with Vue.js (and Inertia.js) and I want to build an select in my form, but my select after compiling is empty and dev console in web browser throwings me this error:
Property or method "options" 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
Please, let me to show you my code for getting help - Index.vue:
<template>
<app-layout>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<div>
<div class="p-6 sm:px-20 bg-white border-b border-gray-200">
<div class="flex items-center justify-end mt-4">
<jet-application-logo class="block h-12 w-auto items-center mx-auto" />
</div>
<div>
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<jet-form-section #submitted="createInvestment">
<template #title>
Investment gateway
</template>
<template #form>
<div class="mx-auto text-center">
<jet-input-amount id="amount" type="number" name="amount" placeholder="Amount for invest" v-model="investmentForm.amount" />
<jet-input-error :message="investmentForm.error('amount')" class="mt-2" />
<select v-model="currency" name="currency" class="input-currency">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
</div>
<div class="mx-auto text-center">
<jet-input id="card_number" type="email" name="card_number" placeholder="Card Number" v-model="investmentForm.paypal" />
<jet-input-error :message="investmentForm.error('card_number')" class="mt-2" />
</div>
<div class="mx-auto text-center nomad-slash">
<jet-input-card id="card_expiration_month" type="email" name="card_expiration_month" placeholder="MM" v-model="investmentForm.paypal" />
<jet-input-error :message="investmentForm.error('card_expiration_month')" class="mt-2" />
/
<jet-input-card id="card_expiration_year" type="email" name="card_expiration_year" placeholder="YY" v-model="investmentForm.paypal" />
<jet-input-error :message="investmentForm.error('card_expiration_year')" class="mt-2" />
<jet-input-card id="card_cvv" type="email" name="card_cvv" placeholder="CVV" v-model="investmentForm.paypal" />
<jet-input-error :message="investmentForm.error('card_cvv')" class="mt-2" />
</div>
</template>
<template #actions>
<jet-action-message :on="investmentForm.recentlySuccessful" class="mr-3">
Your money are on the way!
</jet-action-message>
<jet-button :class="{ 'opacity-25': investmentForm.processing }" :disabled="investmentForm.processing">
Invest
</jet-button>
<br /><br />
</template>
</jet-form-section>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</app-layout>
</template>
<script>
import JetActionMessage from './../../Jetstream/ActionMessage'
import JetFormSection from './../../Jetstream/FormSection'
import JetButton from './../../Jetstream/Button'
import JetInput from './../../Jetstream/Input'
import JetInputAmount from './../../Jetstream/InputAmount'
import JetInputCard from './../../Jetstream/InputCard'
import JetLabel from './../../Jetstream/Label'
import JetInputError from './../../Jetstream/InputError'
import JetApplicationLogo from './../../Jetstream/ApplicationLogo'
import AppLayout from "./../../Layouts/AppLayout";
export default {
name: "Index",
components: {AppLayout, JetInput, JetLabel, JetFormSection, JetButton, JetActionMessage, JetInputError, JetApplicationLogo, JetInputAmount, JetInputCard},
data() {
return {
investmentForm: this.$inertia.form({
amount: '',
currency: '',
options: [
{ text: '$ USD', value: 'usd' },
{ text: 'R ZAR', value: 'zar' },
{ text: '€ EUR', value: 'eur' },
{ text: '£ GBP', value: 'gbp' },
{ text: '₹ INR', value: 'inr' },
{ text: '$ AUD', value: 'aud' },
],
card_number: '',
card_expiration_month: '',
card_expiration_year: '',
card_cvv: '',
}, {
bag: 'createInvestment',
resetOnSuccess: false,
}),
}
},
methods: {
createInvestment() {
this.investmentForm.post('/input/create', {
preserveScroll: true,
});
}
}
}
</script>
<style scoped>
</style>
In your v-for loop, VueJS is trying to find options key in your data object. Your options are under investmentForm key, so in your v-for, instead of
v-for="option in options"
You should write
v-for="option in investmentForm.options"
The error you are getting simply means that Vue does not know what options is as it can't find it anywhere.
options is inside investmentForm data so you need to update this
<option v-for="option in investmentForm.options" v-bind:value="option.value">
{{ option.text }}
</option>
options is inside investmentForm, so it must be invoked in the template like this investmentForm.options.
An alternative to the solutions already given would be to add a computed method, this way you don't need to change the template:
// ...
computed: {
options() {
return investmentForm.options;
}
},
// ...

Dynamically add a child component in Vue JS

I need some help in Vue JS and Laravel with adding a child vue component.
I have a parent component called "wrapper" and some child components called like "show-1", "show-2", "show-3" ... etc.
Parent component:
<template>
<div class="card border-light">
<div class="card-header">
<h5 class="title">{{ title }}</h5>
</div>
<div class="card-body">
<component
is="view"
></component >
</div>
<div class="card-footer"></div>
</div>
</template>
<script>
export default {
props : ['title'],
data() {
return {
view : ''
}
}
}
</script>
An exmaple child component like "show-1":
<template>
<div> show-1 </div>
</template>
This code below is in blade for rendering wrapper component with a dynamic child component name:
<wrapper
title="Example"
view="show-1"
></wrapper>
This code is not working but if i change the parent view data "show-1" instead of empty, it works. why ?
When I change the view prop, child vue component should be changed too. How could I do this ?
I want to pass the view attribute to parent component dynamically.
You can use :is attribute. You can read more about it here:
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
You can use the same mount point and dynamically switch between
multiple components using the reserved element and
dynamically bind to its is attribute....
<template>
<div class="card border-light">
<div class="card-header">
<h5 class="title">{{ title }}</h5>
</div>
<div class="card-body">
<!-- make sure to use : -->
<component v-if="view" :is="view"></component >
</div>
<div class="card-footer"></div>
</div>
</template>
<script>
export default {
props : ['title'],
data() {
return {
view : ''
}
}
}
</script>
#Eduardo has the right answer. To add to it, import your components into the parent and switch between them via a data property:
...
<component :is="current"></component >
...
data: {
current: 'show1'
},
components: {
show1: Show1Component,
show2: Show2Component,
show3: Show3Component
}
The key is to bind the component using the name of the dynamic component.
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components

Categories