Change component prop Vue - vuejs2

I'm storing a data in pinia store:
text: "Complete",
I'm passing this text as a prop to a component:
<SmallButton
:title="store.text"
#click="markComplete()"
/>
Inside the component, the setup function looks like this:
setup(props) {
let newTitle = ref(props.title);
...
}
And the newTitle text is shown in a div:
<div :class="text-caption q-mx-xs ">{{ newTitle }}</div>
Right now if the store.text is changed, the newTitle text stays the same. I also tried to use store.text inside the div, but nothing happened.
How can I solve this?

Solution for Vue3
https://stackblitz.com/edit/vue-t4xx8b?file=src/components/SmallButton.vue
<template>
<button :title="title" #click="$emit('click')">{{ title }}</button> <br />
</template>
<script>
export default {
name: 'SmallButton',
props: ['title'],
};
</script>

Related

Why do I need the v-bind when I have the v-on?

In the tutorial of vue.js, we have this code
<script>
export default {
data() {
return {
text: ''
}
},
methods: {
onInput(e) {
this.text = e.target.value
}
}
}
</script>
<template>
<input :value="text" #input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</template>
And I don't understand why when I delete the bind on value, the two way binding is still working ?
In the tuto, it says that using the v-on & v-bind allow to do two way binding
Am I missing something ?
The Vue example is sort of a bad use case, a little simple for what it's trying to convey:
v-on is for assigning event listeners, so v-on:click="doSomething(value)"
v-bind is binding the actual value of vue data/state. So example:
<button v-on:click="setUserDetails(value)" v-bind:value="user.id">Click</button>
Imagine this component:
<template>
<input :value="value"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
And now a simple usage of it:
<template>
<MyComp v-model="passwd" type="password" minlength="3" #focus="onFocus"/>
</template>
<script>
export default {
name: 'MyOtherComp',
data(){
return {
passwd: ''
}
},
methods:{
onFocus(){}
}
}
</script>
As you can see, value, type, and minlength properties and focus event are bidden to MyComp.
Now question: How can I handle extra props in MyComp? they are not defined in MyComp props. Vue gathers them in a special variable called $attrs, which is a normal JS object. Vue also gathers all events into $listeners variable.
Now inside MyComp these special variables are:
$atrrs:{
type: 'password',
minlength: '3'
}
$listerners:{
focus: /* function onFocus from parent */
}
To redirect these values:
<template>
<input :value="value" v-bind="$attrs" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
As you can see, we use v-bind to bind extra props, and we use v-on to bind (redirect) events. The result is:
<input :value="value" :type="$attrs.type" :minlength="$attrs.minlength" #focus="$listeners.focus"/>
Of course you can use these directions to bind you objects too:
<template>
<input :value="value" v-bind="$attrs" v-bind="accumulated" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
},
data(){
return {
accumulated:{
maxlenght: (+this.$attrs.minlength || 2) + 30, // It's just for a practice to use extra props inside JS code :-)
rows: 5,
}
}
}
}
</script>
Keep in mind that duplicate props will replace and the last one wins.

Teleport in component from slot Vue3

I want to create tabs component for my components library. I want tabs and tab components to work like this:
<b-tabs>
<b-tab
:title="'tab 1'"
:is-active="false"
>
tab content1
</b-tab>
<b-tab
:title="'tab 2'"
:is-active="false"
>
tab content2
</b-tab>
<b-tab
:title="'tab 3'"
:is-active="true"
>
tab content3
</b-tab>
</b-tabs>
So we have two components and they have some props including is-active which by default will be false.
The parent component - tabs.vue will be something like this
<template>
<section :class="mode ? 'tabs--light' : 'tabs--dark'" #change-tab="selectTab(2)">
<div :id="`tabs-top-tabId`" class="tabs__menu"></div>
<slot></slot>
</section>
</template>
here we have wrapper for our single tab which will be displayed here using slot. Here in this "parent" component we are also holding selectedIndex which specify which tab is selected and function to change this value.
setup () {
const tabId = Math.random() // TODO: use uuid;
const data = reactive<{selectedIndex: number}>({
selectedIndex: 0
})
const selectTab = (i: number) => {
data.selectedIndex = i
}
return {
tabId,
...toRefs(data),
selectTab
}
}
TLDR Now as you guys might already noticed in tab.vue I have div with class tabs__menu which I want to teleport some stuff into. As the title props goes into <tab> component which is displayed by the slot in tabs.vue I want to teleport from tab to tabs.
My tab.vue:
<template>
<h1>tab.vue {{ title }}</h1>
<div class="tab" v-bind="$attrs">
<teleport :to="`#tabs-top-tabId`" #click="$emit('changeTab')">
<span style="color: red">{{ title }}</span>
</teleport>
<keep-alive>
<slot v-if="isActive"></slot>
</keep-alive>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
export default defineComponent({
props: {
title: {
type: String as PropType<string>,
required: true
},
isActive: {
type: Boolean as PropType<boolean>,
required: true
}
// tabId: {
// type: Number as PropType<number>, // TODO: change to string after changing it to uuid;
// required: true
// }
}
})
</script>
However this span does not get teleported. When I run first snippet for this post I can't see it displayed and I don't see it in DOM.
Why teleported span doesnt display?
I came across this issue recently when using element-plus with vue test utils and Jest.
Not sure if this would help but here is my workaround.
const wrapper = mount(YourComponent, {
global: {
stubs: {
teleport: { template: '<div />' },
},
},
})

Parent variable not updated when updating trough child component

I am trying to create a few custom form fields for my page and i learned that i cannot use props to do so so i am trying to find a way to update my parent component variable when i use my child component. Whe i check the parent variable it is always empty.
Here is my component:
<template>
<input
v-model="value"
:placeholder="placeHolder"
class="form-field"
>
</template>
<script>
export default {
props: ['placeHolder'],
data() {
return {
value: ''
}
},
methods: {
updateValue(){
this.$emit("update-text", this.value);
}
},
watch: {
value: function(){
this.updateValue
}
}
}
</script>
And this is how i use the component:
<TextField placeholder="Nome" :update-text="name = value"/>
what exactly am i doing wrong?
I am using vue.js with nuxt.js
I think a simpler approach in this case might be emitting an input event from your custom text field and binding the component to the variable using v-model.
TextField.vue
<template>
<input
#input="$emit('input', $event.target.value)"
:placeholder="placeHolder"
class="form-field"
>
</template>
<script>
export default {
props: ['placeHolder']
}
</script>
Usage
<template>
<TextField placeholder="Nome" v-model="name"/>
</template>
<script>
export default {
data: () => ({
name: '',
}),
}
</script>
Read more about using v-model on custom components here.

Vue - Unable pass specific value to higher component

I am getting the following - Cannot read property 'free' of undefined.
I will be adding this button component on multiple pages and I have data object which will allow me to add text based on whatever page I want displayed on a page. For example if its on the homepage I would like to use <buttons :text="buttonText.free" /> and on about us page I would like to use <buttons :text="buttonText.spend" />
Template file
<template>
<main class="container">
<buttons :text="buttonText.free" />
</main>
</template>
<script>
import Buttons from '~/components/atoms/buttons.vue'
export default {
components: {
Buttons
}
}
</script>
Component file
<template>
<div>
<button class="button"">
<span>{{ buttonText }}</span>
</button>
</div>
</template>
<script>
export default {
props: {
text: String
},
data () {
return {
buttonText: {
free: 'free',
spend: 'spend',
now: 'now',
nowFree: 'now free'
}
}
}
}
</script>
Could you tell me what I am doing wrong?
You should define your data in your parent component's data property. All the variables that is used inside the template tag will be fetched from data, computed or props of the component. You are passing an undefined buttonText data to your buttons component.
<template>
<main class="container">
<buttons :text="buttonText.free" />
</main>
</template>
<script>
import Buttons from '~/components/atoms/buttons.vue'
export default {
data() {
return {
buttonText: {
free: 'free',
spend: 'spend',
now: 'now',
nowFree: 'now free'
}
}
},
components: {
Buttons
}
}
</script>
and in your buttons component, just accept the props passed by the parent component. In this case, you are using text as the props of the buttons component.
<template>
<div>
<button class="button"">
<span>{{ text }}</span>
</button>
</div>
</template>
<script>
export default {
props: {
text: String
}
}
</script>
template.vue
<template>
<main class="container">
<buttons :text="your customized text" />
</main>
</template>
<script>
import Buttons from '~/components/atoms/buttons.vue'
export default {
components: {
Buttons
}
}
</script>
buttons.vue
<template>
<div>
<button class="button">
<span>{{ text }}</span>
</button>
</div>
</template>
<script>
export default {
props: {
text: String
}
}
</script>
here is a simple solution to solve your problem
but you need to learn more fundamentals on vue components
vue component doc

VueJS2: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders

I'm creating very simple Popup Modal Using Vuejs2 and TailwindCss. However i encounter the error like below, when i'm tring to click on Button..
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.
In Parent Component
// CardModal
<template>
<div class="bg-white">
<div v-if="showing">
Modal
</div>
</div>
</template>
<script>
export default {
data() {
return {
showing: false,
}
}
}
</script>
Child Components
<button #click="showing = true" class="px-4 my-4 mx-3 bg-blue-400 py-1 font-bold rounded text-white">
Add Product
</button>
<!-- Modal -->
<cardModal :showing="showing" />
// Script
props: {
showing: {
// required: true,
type: Boolean,
}
},
Thanks in advance...
It's hard to understand your code but you can't change value of prop in your child component directly instead you can emit an event to your parent which change the value of prop for you.
e.g your child component which has
<template>
#click="$emit('show',true)"
</template>
//
props: {
showing: {
// required: true,
type: Boolean,
}
}
your parent
<cardModal :showing="showing" #show="showing=$event" />