I'm trying to pass data from a component through the parent to another child components props but the prop stays undefined in vue tools.
I tried having another variable stored in data and setting its value in the watcher for the prop but it stays undefined aswell.
Here's a snippet of what I'm trying to do.
App.vue
<template>
<NameDisplayer :name="name" />
<NameChanger #update="(n) => updateDisplayer(n)"/>
</template>
<script>
import NameDisplayer from './NameDisplayer.vue';
import NameChanger from './NameChanger.vue';
export default {
components:{
NameDisplayer,
NameChanger
},
data: function(){
return{
name: ""
}
},
methods: {
updateDisplayer(newVal){
this.name = newVal;
}
}
}
</script>
NameDisplayer.vue
<template>
<span>{{ name }}</span> //Name is always empty
</template>
<script>
export default:{
name:"NameDisplayer",
props:{
name: String
},
watch:{
name(newV, oldV){
console.log("Watcher: ", newV, oldV); //This prints out the correct values
}
}
}
</script>
NameChanger.vue
<template>
<span></span>
<input type="text" v-model="name" />
<button #click="updateName()">Update</button>
</template>
<script>
export default{
name:"NameChanger",
data: function(){
return{
name: ""
}
},
methods:{
updateName(){
this.$emit('update', this.name);
}
}
}
</script>
Related
I have the following working code for a search input using options API for component data, watch and methods, I am trying to convert that to the composition api.
I am defining props in <script setup> and also a onMounted function.
<template>
<label for="search" class="hidden">Search</label>
<input
id="search"
ref="search"
v-model="search"
class="border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm h-9 w-1/2"
:class="{ 'transition-border': search }"
autocomplete="off"
name="search"
placeholder="Search"
type="search"
#keyup.esc="search = null"
/>
</template>
<script setup>
import {onMounted} from "vue";
const props = defineProps({
routeName: String
});
onMounted(() => {
document.getElementById('search').focus()
});
</script>
<!--TODO convert to composition api-->
<script>
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
// page.props.search will come from the backend after search has returned.
search: this.$inertia.page.props.search || null,
};
},
watch: {
search() {
if (this.search) {
// if you type something in the search input
this.searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
this.$inertia.get(route(this.routeName));
}
},
},
methods: {
searchMethod: _.debounce(function () {
this.$inertia.get(
route(this.routeName),
{ search: this.search }
);
}, 500),
},
});
</script>
What I am trying to do is convert it to the composition api. I have tried the following but I can't get it to work at all.
let search = ref(usePage().props.value.search || null);
watch(search, () => {
if (search.value) {
// if you type something in the search input
searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
Inertia.get(route(props.routeName));
}
});
function searchMethod() {
_.debounce(function () {
Inertia.get(
route(props.routeName),
{search: search}
);
}, 500)
}
Any help or pointers in how to convert what is currently in <script> into <script setup> would be greatly appreciated thanks.
I managed to get this working with the below!
<script setup>
import {onMounted, ref} from "vue";
import {Inertia} from "#inertiajs/inertia";
const props = defineProps({
route_name: {
type: String,
required: true
},
search: {
type: String,
default: null
}
});
const search = ref(props.search);
onMounted(() => {
search.value.focus();
search.value.addEventListener('input', () => {
if (search.value.value) {
searching();
} else {
Inertia.get(route(props.route_name));
}
});
});
const searching = _.debounce(function() {
Inertia.get(route(props.route_name), {search: search.value.value});
}, 500);
</script>
I'm trying to run a component unit test on Nuxt 3 but I get an error telling me that the component cannot be found..
FAIL test/components/button.test.ts [ test/components/button.test.ts ]
Error: Failed to resolve import "#/components/Texts/Button/ButtonText.vue" from "components\Button\Button.vue". Does the file exist?
button.spec.ts
import {test, expect} from 'vitest';
import {mount} from '#vue/test-utils';
import Button from "../../components/Button/Button.vue";
test('Button Component', async () => {
const button = mount(Button, {
props: {
text: 'My Test Button'
}
});
expect(button.text()).toEqual('My Test Button');
});
Button.vue
<template>
<div class="Button">
<slot name="left" />
<ButtonText :text="text" />
<slot name="right" />
</div>
</template>
<script lang="ts">
export default {
name: 'Button',
components: {ButtonText},
props: {
// Text to display in the button
text: {
type: String as () => string,
default: 'Button',
required: true,
},
}
}
</script>
any ideas ?
Assuming, that #/components/Texts/Button/ButtonText.vue actually exists, a solution to your problem might be adding aliases to your ./vitest.config.ts like that:
// vitest.config.ts
import { defineConfig } from 'vite'
import { aliases } from './aliases'
export default defineConfig({
resolve: { aliases },
// ... further settings
})
// aliases.ts
import { resolve } from 'path';
const r = (p: string) => resolve(__dirname, p);
export const alias: Record<string, string> = {
'~~': r('.'),
'~~/': r('./'),
'##': r('.'),
'##/': r('./'),
// ... other aliases
};
I need to reactively change my component when a field in passed object changes.
<template>
<my-component :prop="prop" />
</template>
<script>
export default {
data() {
return {
prop: {
key: 'value',
flag: true
}
}
}
}
</script>
mycomponent.vue
<template>
<div v-if="flag">Yay, it's a flag!</div>
<div v-else>I am very sad rn</div>
</template>
<script>
export default {
props: {
prop: Object
},
setup(props) {
const prop = ref(props, 'prop')
const flag = // *
return { flag }
}
}
</script>
Don't know what to do here, prop.flag, prop.value.flag doesn't work.
I also tried something like const flag = ref(prop, 'flag') and then flag.value, or even const flag = req(prop.value, 'flag'), but no luck.
Props are accessible and reactive in components should you declare them. Since you haven't, they won't be available.
For example, this is all you need:
<template>
<div v-if="prop.flag">Yay, it's a flag!</div>
<div v-else>I am very sad rn</div>
</template>
<script>
export default {
props: {
prop: Object
}
}
</script>
Just use toRef or toRefs
<template>
<div v-if="flag">Yay, it's a flag!</div>
<div v-else>I am very sad rn</div>
</template>
<script>
import { toRefs, toRef } from 'vue';
export default {
props: {
prop: Object
},
setup(props) {
const { prop } = toRefs(props);
//alternative
const prop = toRef(props, 'prop');
const flag = // *
return { flag }
}
}
</script>
Consider a component below, which accepts tag as props.
<template>
<input v-model="model"/>
</template>
<script>
export default {
name: "InputComponent",
props: {
tag: {
type: String,
required: false,
default: null,
}
}
}
</script>
I want passing the props div as tag value should return dom below.
<div>
<input v-model="model"/>
</div>
Solution with composition api is advantange.
<template>
<component :is="tag">
<input v-model="model"/>
</component>
</template>
<script>
export default {
name: "InputComponent",
props: {
tag: {
type: String,
required: false,
default: null,
}
}
}
</script>
should work.
I'm making a captcha component using vue. I try to fetch the captcha when the component created. When I do this in a async manner, the page will not be reactive, although the state has already been updated. But I when do it in sync manner, everything is fine. So, I'm wondering why async manner won't work?
This works
<template>
<section>
<div class="captcha-container">
<div v-if="captcha" v-html="captcha.data"></div>
</div>
</section>
</template>
<script>
import { mapState, mapActions } from "Vuex";
export default {
created: function() {
this.$store.commit('setCaptcha', {id: 'xx', data:'Hi'});
},
computed: {
...mapState(["captcha"])
},
};
</script>
This does not work
<template>
<section>
<div class="captcha-container">
<div v-if="captcha" v-html="captcha.data"></div>
</div>
</section>
</template>
<script>
import { mapState, mapActions } from "Vuex";
export default {
created: function() {
setTimeout(() => {
this.$store.commit('setCaptcha', {id: 'xx', data:'Hi'});
}, 1000);
},
computed: {
...mapState(["captcha"])
},
};
</script>