How to access child's props from wrapper component in Vue? - vue.js

I have a bunch of Vue components on the marketing website. There are stateful and functional (aka stateless) components. For example badge and button. I need to create a wrapper component for a testing purpose, that takes any component, renders that component but additionally adds a table below with props-values, passed along with child component. For instance:
<XButton hello="hi" :some-other-prop="2" label="Hey">You Will Never Appear</XButton>
will be rendered to a Button tag with the information below:
<button>Such text with label Hey</button>
some-other-prop | 2
label | Hey
Button.js
Vue.component('XButton', {
name: 'XButton',
functional: true,
props: {
label: {
type: String,
default: 'Default text',
},
},
render(createElement, context) {
return createElement('button', context.data, 'Such text with label ' + context.props.label);
},
});
Wrapper.vue
<template>
<div>
<slot/>
</div>
</template>
<script>
export default {
functional: true,
props: {
listPropsOfDoubleNestedChild: {
type: Boolean,
default: false
}
},
render(createElement, context) {
const vNode= context.props.listPropsOfDoubleNestedChild
? context.children[0].children // or even get props of double nested child
: context.children[0];
if (vNode.componentOptions) {
console.log("Props of nested stateful component", vNode.componentOptions.propsData);
} else {
console.log("Props of nested stateless component are not reachable thru componentOptions", vNode.componentOptions);
}
return null;
}
</script>
App.vue
<template>
<Wrapper>
<XButton hello="hi" :some-other-prop="2" label="Hey">You Will Never Appear</XButton>
</Wrapper>
</template>
So, I can only get passed props of the stateful component.
Some screenshots:
Stateful component:
Functional component:
In React it is very simple to accomplish with React.Children.only(props.children).props and what about Vue?

Related

how to validate child form from parent component in Vue

I have a child component which includes form:
<el-form :model="abc" ref="ruleForm" :rules="rules">
<el-form-item prop="files">
<abc-card :title="getTranslation('abc.files')">
<file-selector v-model="abc.files" />
</abc-card>
</el-form-item>
</el-form>
And I want to add simple validations to this form:
rules: function () {
return {
files: [
{
type: 'object',
required: true,
trigger: 'change',
message: 'Field required',
},
],
};
},
But my click button is in the parent component:
<files v-model="editableAbc" ref="editableTab" />
<el-button type="primary" #click="submitForm()">Create</el-button>
methods: {
submitForm() {
this.$refs.form.validate((isValid) => {
if (!isValid) {
return;
}
////API CALLS////
});
},
}
So I am trying to achieve that when the button is clicked the navigation should be rendered. How can I do that?
As per your requirement, My suggestion would be to use a ref on child component to access its methods and then on submit click in parent component, trigger the child component method.
In parent component template :
<parent-component>
<child-component ref="childComponentRef" />
<button #click="submitFromParent">Submit</button>
</parent-component>
In parent component script :
methods: {
submitFromParent() {
this.$refs.childComponentRef.submitForm();
}
}
In child component script :
methods: {
submitForm() {
// Perform validations and do make API calls based on validation passed.
// If you want to pass success or failure in parent then you can do that by using $emit from here.
}
}
The "files" component is the form you're talking about?
If so, then ref should be placed exactly when calling the 'files' component, and not inside it. This will allow you to access the component in your parent element.
<files v-model="editableAbc" ref="ruleForm" />
There is a method with the props, which was mentioned in the comments above. I really don't like it, but I can tell you about it.
You need to set a value in the data of the parent component. Next you have to pass it as props to the child component. When you click the button, you must change the value of this key (for example +1). In the child component, you need to monitor the change in the props value via watch and call your validation function.
// Parent
<template>
<div class="test">
<ChildComponent />
</div>
</template>
<script>
export default {
data() {
return {
updateCount: 0,
};
},
methods: {
submitForm() {
// yout submit method
this.updateCount += 1;
},
},
};
</script>
// Child
<script>
export default {
props: {
updateCount: {
type: Number,
default: 0,
},
},
watch: {
updateCount: {
handler() {
this.validate();
},
},
},
methods: {
validate() {
// yout validation method
},
},
};
</script>
And one more solution. It is suitable if you cannot place the button in the child component, but you can pass it through the slot.
You need to pass the validate function in the child component through the prop inside the slot. In this case, in the parent component, you will be able to get this function through the v-slot and bind it to your button.
// Parent
<template>
<div class="test">
<ChildComponent>
<template #button="{ validate }">
<button #click="submitForm(validate)">My button</button>
</template>
</ChildComponent>
</div>
</template>
<script>
import ChildComponent from "./ChildComponent";
export default {
components: {
ChildComponent,
},
methods: {
submitForm(cb) {
const isValid = cb();
// your submit code
},
},
};
</script>
// Child
<template>
<div class="child-component">
<!-- your form -->
<slot name="button" :validate="validate" />
</div>
</template>
<script>
export default {
methods: {
validate() {
// yout validation method
console.log("validate");
},
},
};
</script>

Pass data from child to parent component with click event(data) in Vue

i need pass data (a slug) from a #click button to a function with $emit event, and then in the Parent component recive this and embebed in another component as a prop, to load a simple object.
This is my approach, but did not work
<-- Child Component (Conversaciones.vue) -->
<template v-slot:title>
<q-btn #click="SlugHijoPadre(conversacion.slug)">
{{ conversacion.contenido }}
</q-btn>
</template>
methods:{
SlugHijoPadre(Slugdata) {
this.$emit("enviar-conversacion", {
slug: Slugdata
})}}
<-- Parent Component -->
<Conversaciones #enviar-conversacion="slugConversacion"/>
components: {
Conversaciones,
Conversacion
},
data() {
return {
slugs: {}
};
},
methods: {
slugConversacion(slug){
this.slugs.push (slug);
console.log(slug);
}
},
//And finally pass a property to call an especific object of another component:
<Conversacion :slug="slugs"></Conversacion>
Check your slugs property, it's an object not an array so you can't use this.slugs.push. Change it to slugs: []

How to bind a local component's data object to an external component

how do you use a local component's data attriutes to bind an external component's v-model
for example i have this component
<publish-blog>
<VueTrix v-model="form.editorContent">
</publish-blog>
so the form.editorContent there refers to the publish-blog component's form.editorContent inside data, how do I do that ?
You can pass a prop to the publish-blog component.
This would be what ever page or component you are using the publish blog on, though to be honest I'm not sure why you would not just put the VueTrix component inside of the publish-blog component.
This would be on what ever page/component you are wanting it on.
<template>
<PublishBlog :trix="trix">
<VueTrix v-model="trix" />
</PublishBlog>
</template>
<script>
import PublishBlog from './PublishBlog.vue';
export default {
components: {
PublishBlog,
},
data() {
return {
trix: '',
};
},
};
</script>
and inside of the publish blog component make the form.editorContent the prop passed or a default value.
But without a global store/state you are stuck with props.
UPDATE: Showing what a publish blog component might look like
PublishBlog.vue
<template>
<section>
what ever goes here.
<slot />
</section>
</template>
<script>
export default {
name: 'PublishBlog',
props: {
trix: {
type: String,
default: '',
},
},
data() {
return {
form: {
editorContent: this.trix
},
};
},
};
</script>

Vue component wrapping

What is the correct way to wrap a component with another component while maintaining all the functionality of the child component.
my need is to wrap my component with a container, keeping all the functionality of the child and adding a trigger when clicking on the container outside the child that would trigger the child`s onclick event,
The parent component should emit all the child component events and accept all the props the child component accepts and pass them along, all the parent does is add a clickable wrapper.
in a way im asking how to extend a component in vue...
It is called a transparent wrapper.
That's how it is usually done:
<template>
<div class="custom-textarea">
<!-- Wrapped component: -->
<textarea
:value="value"
v-on="listeners"
:rows="rows"
v-bind="attrs"
>
</textarea>
</div>
</template>
<script>
export default {
props: ['value'], # any props you want
inheritAttrs: false,
computed: {
listeners() {
# input event has a special treatment in this example:
const { input, ...listeners } = this.$listeners;
return listeners;
},
rows() {
return this.$attrs.rows || 3;
},
attrs() {
# :rows property has a special treatment in this example:
const { rows, ...attrs } = this.$attrs;
return attrs;
},
},
methods: {
input(event) {
# You can handle any events here, not just input:
this.$emit('input', event.target.value);
},
}
}
</script>
Sources:
https://www.vuemastery.com/conferences/vueconf-us-2018/7-secret-patterns-vue-consultants-dont-want-you-to-know-chris-fritz/
https://zendev.com/2018/05/31/transparent-wrapper-components-in-vue.html

How to pass mixins as props and use them in child components using Vue.js

I have a parent component which contains two child components called add and edit, these components have some common properties and i want to use mixins, for that i add an object called mix to the parent's data object and i pass it as props to child components as follow
the parent component :
<template>
<div id="app">
<add :mixin="mix" operation="add"></add>
...
<edit :mixin="mix" operation="edit"></edit>
</div>
</template>
<script>
export default {
name: "App",
data(){
return{
/****/
mix:{
data() {
return {
user: { name: "", email: "" },
users: []
};
},
methods: {
add() {
this.users.push(this.user);
},
}
}
}
/*****/
};
},
components: {
add,edit
}
};
</script>
I could receive that object (mix) in my child component, but how could i assign it to the mixins property?
A low hanging fruit kind of way to solve this would be to just refactor your code and write the mixin in a separate file. You can then import the mixin object in both of your components and assign it to the mixins property.