using props to show child component - vue.js

i want to show my child component, but it give me error in browser
[Vue warn]: Property "modules" was accessed during render but is not defined on instance.
i have parent component that i use props to send boolean value to child component;
parent component
<template>
// i am tring to props visible value here
<SlidePage :visible="showForm"></SlidePage>
// i am tring to change value of showForm
<button label="hide manufacturer" #click="showForm = true">Show</button>
</template>
<script setup>
import SlidePage from '#/components/parts/storypage/SlidePage.vue'
import { ref } from 'vue'
const showForm = ref(false)
</script>
child component
<template>
// get the cisible from props
<div class="modal" v-if="visible">
<p class="title has-text-white">Modal title</p>
</div>
</template>
<script setup>
import { defineProps } from 'vue'
// eslint-disable-next-line no-unused-vars
const props = defineProps({
visible: {
type: Boolean,
required: true,
},
})

you cannot send props from parent to child component. You can use provide() function to pass value to child component and inject() to receive the value that you want to pass. details https://vuejs.org/guide/components/provide-inject.html#prop-drilling

Related

VueJS: Template Refs binding from slot props?

I have a component with a slot (SlotComponent) like this for example
<template>
<slot :element="element"></slot>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
const element = ref<HTMLElement | null>(null);
onMounted(() => console.log(element.value));
</script>
However I can't seems to bind the element within the slot when using the component. The element is null on the onMounted lifecycle callback (above snippet).
<SlotComponent v-slot="{ element }">
<div ref="element">hello world</div>
</SlotComponent>
Question: how can I bind to the html element inside the slot?
use Function Refs
provide a setElement() function as a slot prop
<template>
<slot :set-element="setElement"></slot>
</template>
<script setup lang="ts">
import { ref, watchEffect } from "vue";
const element = ref<Element | null>(null);
function setElement(el: Element) {
element.value = el;
}
watchEffect(() => {
console.log(element.value);
});
</script>
usage
<SlotComponent v-slot="{ setElement }">
<div :ref="(el) => setElement(el)">Hello World</div>
</SlotComponent>

Access components method via template ref

When I have a child component like this:
<script setup>
import { defineExpose } from 'vue'
const validate = () => {
console.log('validate')
}
defineExpose({ validate })
</script>
<template>
hello
</template>
and parent component in which I use child:
<script setup>
import { ref } from 'vue'
const test = ref()
const validate = () => {
console.log('test', test.value)
}
</script>
<template>
<div ref="test">
<Child />
</div>
<button #click="validate">
click me
</button>
</template>
Is it possible to access validate method from the child component via template ref which is on the wrapper div in parent component?
EDIT:
I update my playground link in which I completed the task but I'm using parent instance instead of provide/inject:
https://sfc.vuejs.org/#eNqNU9FuozAQ/BWfXyBSYt4jqK463Un9hqOqKCypW7At29BWEf/eXcAkJVXaSEHszu4wnl0f+a0xou+A73nqSiuNZw58Z25yJVujrWdHZqFmA6utblmEpdEC/dO2nfMioYCYTvCdMp1f8DGaC0qtHCLUnhF9vMkVYyHvAR8Zizcsu2FHQqhS9EXTAT1lVXigliFXaTKpRr0YeGhNgyBFPh3lIXuWcyLIOaYZ/tJJWPKDMB2PNb6Gf/rYea8V+102snxBbpK7cI9J1sLUPJUilCaLNL7lkz+7tjDi2WmF3o+nzGfA5Xw/nZty6BjFOX/y3rh9kri6JBufndD2kOCbsJ3ysgUBrt09Wv3qwCJxzrdnHAkme7A7C6oCC/Ya56r0gpdo0fsBjxKmfm1/Kqilgr9vRjtYLVIY+TxVqdWttcU7Tv///QqDi5VgcQPnrUzXa6JN8PGUn3aN5NPnz7XFx+Vb2wtFA7ZdW7ZK9mGBXKNP+zPlP89/uQrXXDNW97JCJQfwfzqLw/B3aEehym9MXBlFmG5ANPoQR0uJJAm/oukSBQIZ+LMvPjr6hvpqFoc6YQqqEP7dgHh4UEWLrVnGItqKaPZ+XQyj11W4yMFgYTr3FAd931/un/s9fAD8ILMq
How to actually get rid of parent instance and use provide inject to achieve same result as in the playground from link above?
The ref needs to be on the actual Child element, not the parent div. The method is a property of test.value, so if the method is called "validate" you can run it with test.value.validate().
You also need to make sure the Child component is imported
Try this SFC Playground instead. The "click me" button will console.log the word "validate" which comes from the Child component.
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const test = ref()
const childFunc = () => {
test.value.validate()
}
</script>
<template>
<div>
<Child ref="test" />
</div>
<button #click="childFunc">
click me
</button>
</template>

How to control the visibility of a child component from the parent using a prop?

I'm trying to control the visibility of a child component from the parent using a prop.
My child component has a visible prop. To avoid mutating the visible prop I assigned it to a local isVisible ref, and I'm using that to conditionally show and hide a form.
But when I try to hide the form using a button with #click="isVisible = false" nothing happens, and I get a console error saying Set operation on key "visible" failed: target is readonly.
It's also confusing why the error messsage is referring to the visible prop because I am using the local isVisible variable instead.
This is the ChildForm.vue (child component):
<template>
<q-form v-if="isVisible">
<q-input v-model="modelValue" />
<q-btn label="Hide The Form" #click="isVisible = false" />
</q-form>
</template>
<script setup lang="ts">
import { ref, toRef } from 'vue';
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
});
const isVisible = toRef(props, 'visible');
const modelValue = ref('');
</script>
This is the ParentPage.vue (parent component):
<template>
<child-form :visible="showForm" />
<q-btn label="Show the Form" #click="showForm = true" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ChildForm from 'src/components/ChildForm.vue';
const showForm = ref(false);
</script>
toRef() retains the reactive connection to the original source, so modifying isVisible effectively modifies the original prop, which is supposed to be readonly, leading to the warning you observed.
But I think you're actually trying to keep the child's visible prop in sync with the parent's showForm prop, such that updates in the child are automatically reflected in the parent. v-model is the tool for that problem.
In the parent, bind showForm to v-model:visible:
<child-form v-model:visible="showForm">
In the child, emit an update:visible event with the desired value whenever you want to update the parent:
<template>
<q-form v-if="visible">
<q-input v-model="modelValue" /> 👇
<q-btn label="Hide The Form" #click="$emit('update:visible', false)" />
</q-form>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
})
const modelValue = ref('')
</script>
demo

can't use template ref on component in vue 3 composition api

I want to get the dimensions of a vue.js component from the parent (I'm working with the experimental script setup).
When I use the ref inside a component, it works as expected. I get the dimensions:
// Child.vue
<template>
<div ref="wrapper">
// content ...
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const wrapper = ref(null)
onMounted(() => {
const rect = wrapper.value.getBoundingClientRect()
console.log(rect) // works fine!
})
</script>
But I want to get the dimension inside the parent component. Is this possible?
I have tried this:
// Parent.vue
<template>
<Child ref="wrapper" />
</template>
<script setup>
import Child from './Child'
import { ref, onMounted } from 'vue'
const wrapper = ref(null)
onMounted(() => {
const rect = wrapper.value.getBoundingClientRect()
console.log(rect) // failed!
})
</script>
the console logs this error message:
Uncaught (in promise) TypeError: x.value.getBoundingClientRect is not a function
In the documentation I can only find the way to use template refs inside the child component
does this approach not work because the refs are "closed by default" as the rfcs description says?
I ran into this issue today. The problem is that, when using the <script setup> pattern, none of the declared variables are returned. When you get a ref to the component, it's just an empty object. The way to get around this is by using defineExpose in the setup block.
// Child.vue
<template>
<div ref="wrapper">
<!-- content ... -->
</div>
</template>
<script setup>
import { defineExpose, ref } from 'vue'
const wrapper = ref(null)
defineExpose({ wrapper })
</script>
The way you set up the template ref in the parent is fine. The fact that you were seeing empty object { } in the console means that it was working.
Like the other answer already said, the child ref can be accessed from the parent like this: wrapper.value.wrapper.getBoundingClientRect().
The rfc has a section talking about how/why this works: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md#exposing-components-public-interface
It's also important to note that, with the <script setup> pattern, your ref in the parent component will not be a ComponentInstance. This means that you can't call $el on it like you might otherwise. It will only contain the values you put in your defineExpose.
I don't this this is necessarily related to the <script setup> tag. Even in the standard script syntax your second example will not work as-is.
The issue is you are putting ref directly on the Child component:
<template>
<Child ref="wrapper" />
</template>
and a ref to a component is NOT the same as a ref to the root element of that component. It does not have a getBoundingClientRect() method.
In fact, Vue 3 no longer requires a component to have a single root element. You can define your Child component as :
<template>
<div ref="wrapper1">// content ...</div>
<div ref="wrapper2">// content ...</div>
</template>
<script >
import { ref } from "vue";
export default {
name: "Child",
setup() {
const wrapper1 = ref(null);
const wrapper2 = ref(null);
return { wrapper1, wrapper2 };
},
};
</script>
What should be the ref in your Parent component now?
Log the wrapper.value to your console from your Parent component. It is actually an object of all the refs in your Child component:
{
wrapper1: {...}, // the 1st HTMLDivElement
wrapper2: {...} // the 2nd HTMLDivElement
}
You can do wrapper.value.wrapper1.getBoundingClientRect(), that will work fine.
You could get access to the root element using $el field like below:
<template>
<Child ref="wrapper" />
</template>
<script setup>
import Child from './Child'
import { ref, onMounted } from 'vue'
const wrapper = ref(null)
onMounted(() => {
const rect = wrapper.value.$el.getBoundingClientRect()
console.log(rect)
})
</script
Right, so here's what you need to do:
// Parent component
<template>
<Child :get-ref="(el) => { wrapper = el }" />
</template>
<script setup>
import Child from './Child.vue';
import { ref, onMounted } from 'vue';
const wrapper = ref();
onMounted(() => {
const rect = wrapper.value.getBoundingClientRect()
console.log(rect) // works fine!
});
</script>
and
// Child component
<template>
<div :ref="(el) => { wrapper = el; getRef(el)}">
// content ...
</div>
</template>
<script setup>
import { defineProps, ref, onMounted } from 'vue';
const props = defineProps({
getRef: {
type: Function,
},
});
const wrapper = ref();
onMounted(() => {
const rect = wrapper.value.getBoundingClientRect()
console.log(rect) // works fine!
});
</script>
To learn why, we need to check Vue's documentation on ref:
Vue special-attribute 'ref'.
On dynamic binding of (template) ref, it says:
<!-- When bound dynamically, we can define ref as a callback function,
passing the element or component instance explicitly -->
<child-component :ref="(el) => child = el"></child-component>
Since the prop lets you pass data from the parent to a child, we can use the combination of the prop and dynamic ref binding to get the wanted results. First, we pass the dynamic ref callback function into the child as the getRef prop:
<Child :get-ref="(el) => { wrapper = el }" />
Then, the child does the dynamic ref binding on the element, where it assigns the target el to its wrapper ref and calls the getRef prop function in that callback function to let the parent grab the el as well:
<div :ref="(el) => {
wrapper = el; // child registers wrapper ref
getRef(el); // parent registers the wrapper ref
}">
Note that this allows us to have the ref of the wrapper element in both the parent AND the child component. If you wished to have access to the wrapper element only in the parent component, you could skip the child's callback function, and just bind the ref to a prop like this:
// Child component
<template>
<div :ref="getRef">
// content ...
</div>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
getRef: {
type: Function,
},
});
</script>
That would let only the parent have the ref to your template's wrapper.
If you're seeing the wrapper.value as null then make sure the element you're trying to get the ref to isn't hidden under a false v-if. Vue will not instantiate the ref until the element is actually required.
I realize this answer is not for the current question, but it is a top result for "template ref null vue 3 composition api" so I suspect more like me will come here and will appreciate this diagnosis.

Vue js how to use props values to v-model

I have two component namely App.vue and hello.vue
In App component I import the hello component and use props to pass relevant data to the hello component.
there I bind data which are took from the App component.
In my hello component I have a input box bind to the passed value.
My final goal is pass values as props to the hello component and change it and finally
pass that edited values to the backend using the save method.
How do I achive this?
This is what I have done up to now.
App.vue
<template>
<div id="app">
<hello-world :msg="'hello good morning'"></hello-world>
</div>
</template>
<script>
import helloWorld from "./components/HelloWorld";
export default {
components: {
helloWorld
}
};
</script>
hello.vue
<template>
<div>
<input type="text" :value="msg">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
}
};
</script>
In my hello component's input field v-model is not possible. I want something similar to the v-model.
You cannot use prop to bind to v-model. Child component is not supposed to modify prop passed by the parent component.
You will have to create a copy of prop in your child component if you wish to use prop with v-model and then watch prop like this:
<template>
<div>
<input type="text" #input="onInput" v-model="msgCopy">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
data() {
return { msgCopy: '' };
},
methods: {
onInput(newInputValue) {
this.$emit('msgChange', newInputValue);
}
}
watch: {
msg(newVal) {
this.msgCopy = newVal;
}
}
};
</script>
Also, notice the use of event handler #input to pass changed prop back to the parent component via event. As a syntax sugar, you can make your Hello component work as a custom form input control by adopting to v-model lifecycle.