VueJS warning when using dynamic components and custom-events - javascript

So, I get this warning:
" Extraneous non-emits event listeners (addNewResource) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option."
And I don't understand why. I am using dynamic components with 2 custom events. I've tried adding both of the emitted events to the emits object of both components.
App.vue
<template>
<AppHeader />
<NavigationBar #select-component="selectComponent" />
<keep-alive>
<component
:is="selectedComponent"
v-bind="componentProps"
#delete-resource="deleteResource"
#add-new-resource="addNewResource"
></component>
</keep-alive>
</template>
<script>
import AppHeader from "./components/SingleFile/AppHeader.vue";
import NavigationBar from "./components/NavigationBar.vue";
import LearningResources from "./components/LearningResources.vue";
import AddResource from "./components/AddResource.vue";
export default {
components: {
AppHeader,
NavigationBar,
LearningResources,
AddResource,
},
data() {
return {
selectedComponent: "learning-resources",
learningResources: [
{
name: "Official Guide",
description: "The official Vue.js documentation",
link: "https://v3.vuejs.org",
},
{
name: "Google",
description: "Learn to google...",
link: "https://www.google.com/",
},
],
};
},
methods: {
selectComponent(component) {
this.selectedComponent = component;
},
deleteResource(name) {
this.learningResources = this.learningResources.filter(
(resource) => resource.name !== name
);
},
addNewResource(newResourceObject) {
const newResource = {
name: newResourceObject.title,
description: newResourceObject.description,
link: newResourceObject.link,
};
this.learningResources.push(newResource);
},
},
computed: {
componentProps() {
if (this.selectedComponent === "learning-resources") {
return {
learningResources: this.learningResources,
};
}
return null;
},
},
};
</script>
AddResource.vue
<template>
<base-card>
<template #default>
<form #submit.prevent>
<div>
<label for="title">Title</label>
<input type="text" v-model="newResource.title" />
</div>
<br />
<div>
<label for="description">Description</label>
<textarea rows="3" v-model="newResource.description" />
</div>
<br />
<div>
<label for="link">Link</label>
<input type="text" v-model="newResource.link" />
</div>
<button #click="$emit('add-new-resource', newResource)">
Add Resource
</button>
</form>
</template>
</base-card>
</template>
<script>
import BaseCard from "./Base/BaseCard.vue";
export default {
components: {
BaseCard,
},
emits: ["add-new-resource"],
data() {
return {
newResource: {
title: "",
description: "",
link: "",
},
};
},
};
</script>
LearningResources.vue
<template>
<base-card v-for="resource in learningResources" :key="resource.name">
<template #header>
<h3>{{ resource.name }}</h3>
<button #click="$emit('delete-resource', resource.name)">Delete</button>
</template>
<template #default>
<p>{{ resource.description }}</p>
<p><a :href="resource.link">View Resource</a></p>
</template>
</base-card>
</template>
<script>
import BaseCard from "./Base/BaseCard.vue";
export default {
components: {
BaseCard,
},
props: {
learningResources: Array,
},
emits: ["deleteResource"],
};
</script>

Seems to be because <component> is being used for two separate components, neither of which emit the same event as the other.
One thing you can try is disabling each components' attribute inheritance:
export default {
...
inheritAttrs: false
...
}
If this doesn't suit your needs, you could refactor the logic to handle both emitted events, i.e. rename the events to a shared name like "addOrDeleteResource", then determine which event is being emitted in App.vue and handle it accordingly.

Related

How to forward data from one component and post to another component Vue.js 3

Im very new to Vue.js, Formkit and am a bit lost in how to proceed.
I have one component which creates a uniqueID(unique enough for us) when mounted.
<template>
<div>
<span>
<p class="solid" :for="id"> ID: {{ id }}</p>
</span>
</div>
</template>
<script>
export default {
name: "unique-id",
data() {
return {
id: null
}
},
mounted() {
this.id =
(Date.now().toString(32) +
Math.random().toString(16)).replace(/\./g, '')
}
}
</script>
I then need to submit that id and a couple of other bits of data to another component.
<script>
import uniqueID from "./id.vue";
</script>
export default {
name: "BasicItem-page",
data ()
{
// eslint-disable-next-line no-unused-vars
return {
data:{ }
}
},
components:
{
uniqueID,
}
}
</script>
<template>
<html lang="en">
<FormKit
type="form"
id="BasicItem"
v-model="data"
#submit="submit"
>
<h3>Add Item</h3>
<span>
<uniqueID/>
</span>
<FormKit
type="floatingLabelTextInput"
name="title"
label="Title"
validation="required|text"
/>
<FormKit
type="floatingLabelTextInput"
name="location"
label="Location"
validation="required|text"
/>
<uniqueID/>
<FormKit
type="floatingLabelTextInput"
name="item_title"
label="Item Title"
validation="required|text"
/>
<FormKit
type="floatingLabelTextInput"
name="item_location"
label="Item Location"
validation="required|text"
/>
</template>
I'm using the 'id' component at other places within the above form which also complicates matters
How do I get the ID?
I cant seem to be able to get the ID. Ive tried to create a composable which didnt give any errors but no data.
import { ref } from 'vue'
export function NewuniqueID() {
const id = (Date.now().toString(32) +
Math.random().toString(16)).replace(/\./g, '')
return { id }
}

How to hide content when clicked checkbox from different components in vuejs?

//inputtwo.vue
<template>
<div><input type="checkbox" v-model="checked" />one</div>
</template>
<script>
export default {
name: "inputtwo",
components: {},
data() {
return {};
},
};
</script>
//maincontent.vue
<template>
<div>
<div class="container" id="app-container" v-if="!checked">
<p>Text is visible</p>
</div>
<common />
</div>
</template>
<script>
export default {
name: "maincontent",
components: {},
data() {
return {
checked: false,
};
},
methods: {
hidecont() {
this.checked = !this.checked;
},
},
};
</script>
//inputone.vue
<template>
<div><input type="checkbox" v-model="checked" />one</div>
</template>
<script>
export default {
name: "inputone",
components: {},
data() {
return {};
},
};
</script>
How to hide content of checkbox from different components in Vuejs
I have three components called inputone(contains checkbox with v-model),inputtwo (contains checkbox with v-model),maincontent.(having some content and logic), So when user click on checkboxes from either one checckbox(one,two). i schould hide the content.
Codesanfdbox link https://codesandbox.io/s/crimson-fog-wx9uo?file=/src/components/maincontent/maincontent.vue
reference code:- https://codepen.io/dhanunjayt/pen/mdBeVMK
You are actually not syncing the data between components. The main content checked never changes. You have to communicate data between parent and child components or this won't work. And try using reusable components like instead of creating inputone and inputtwo for same checkbox create a generic checkbox component and pass props to it. It is a good practice and keeps the codebase more manageable in the longer run.
App.vue
<template>
<div id="app">
<maincontent :showContent="showContent" />
<inputcheckbox text="one" v-model="checkedOne" />
<inputcheckbox text="two" v-model="checkedTwo" />
</div>
</template>
<script>
import maincontent from "./components/maincontent/maincontent.vue";
import inputcheckbox from "./components/a/inputcheckbox.vue";
export default {
name: "App",
components: {
maincontent,
inputcheckbox,
},
computed: {
showContent() {
return !(this.checkedOne || this.checkedTwo);
},
},
data() {
return {
checkedOne: false,
checkedTwo: false,
};
},
};
</script>
checkbox component:
<template>
<div>
<input
type="checkbox"
:checked="value"
#change="$emit('input', $event.target.checked)"
/>
{{ text }}
</div>
</template>
<script>
export default {
name: "inputcheckbox",
props: ["value", "text"],
};
</script>
Content:
<template>
<div class="container" id="app-container" v-if="showContent">
<p>Text is visible</p>
</div>
</template>
<script>
export default {
name: "maincontent",
props: ["showContent"]
}
</script>
https://codesandbox.io/embed/confident-buck-kith5?fontsize=14&hidenavigation=1&theme=dark
Hope this helps and you can learn about passing data between parent and child components in Vue documentation: https://v2.vuejs.org/v2/guide/components.html
Consider using Vuex to store and maintain the state of the checkbox. If you're not familiar with Vuex, it's a reactive datastore. The information in the datastore is accessible across your entire application.

How to store a value from another component's data?

I have two components, is there a way to store value from another component's data?
Here is Create.vue
<template>
<div id="main">
<Editor />
//some codes here
</div>
</template>
<script>
import Editor from './_Create_Editor.vue'
export default {
components: { Editor },
data: () => ({
text: ''
}),
}
</script>
And here is the _Create_Editor.vue.
<template>
//sample input for demonstration purposes
<input type="text" class="form-control" v-model="text"/>
</template>
The code above returns an error:
Property or method "text" is not defined on the instance but referenced during render
I want everytime I type the data: text from Create.vue has the value of it.
How can I possibly make this? Please help.
You can do this by using $emit.
Create.vue
<template>
<div id="main">
<Editor
#edit={onChangeText}
/>
//some codes here
</div>
</template>
<script>
import Editor from './_Create_Editor.vue'
export default {
components: { Editor },
data: () => ({
text: ''
}),
methods: {
onChangeText: function (value) {
this.text = value
}
}
}
</script>
_Create_Editor.vue
<template>
//sample input for demonstration purposes
<input
type="text"
class="form-control"
#change="onChange"
/>
</template>
<script>
export default {
methods: {
onChange: function (event) {
this.$emit('edit', event.target.value)
}
}
}
</script>

How to pass elements in a for loop to parent component?

Background I have a child component that loops through an array called "expenseButton" passed from my parent component. Within this for loop are elements that are getting updated. Specifically the "expense".
Child component
<form class="container">
<div class="buttonList" v-for="(expense, index) in expenseButton" :key="index">
<button type="button" #click="expenseButtonClick(expense)">{{expense.expensesKey}}</button>
<input class="textInput" v-model.number="expense.subExpense" type="number" />
</div>
</form>
<script>
export default {
props: {
expenseButton: Array,
},
methods: {
expenseButtonClick(expense) {
expense.expensesValue = expense.expensesValue - expense.subExpense;
}
}
}
</script>
Problem I understand that $emit events can pass data to the parent. However, I am trying to figure the best way to send the updated elements of the array back to the parent component.
The Parent component data
<template>
<expense-button :expenseButton="expenseButton"></expense-button>
</template>
<script>
export default {
components: {
"expense-button": Expenses
},
data() {
return {
expenseButton: [
{"expensesKey":"rent","expensesValue":null,"subExpense":""},
{"expensesKey":"movies","expensesValue":null,"subExpense":""},
{"expensesKey":"clothes","expensesValue":null,"subExpense":""}
],
};
}
}
</script>
I think you should use $emit.
Child component:
<form class="container">
<div class="buttonList" v-for="(expense, index) in expenseButton" :key="index">
<button type="button" #click="expenseButtonClick(expense)">{{expense.expensesKey}}</button>
<input class="textInput" v-model.number="expense.subExpense" type="number" />
</div>
</form>
<script>
export default {
props: {
expenseButton: Array,
},
methods: {
expenseButtonClick(expense) {
expense.expensesValue = expense.expensesValue - expense.subExpense;
this.$emit("expense-btn-clicked", expense)
}
}
}
</script>
Parent component:
<template>
<expense-button :expenseButton="expenseButton" #expense-btn-clicked="btnClickedHandler"></expense-button>
</template>
<script>
export default {
components: {
"expense-button": Expenses
},
data() {
return {
expenseButton: [
{"expensesKey":"rent","expensesValue":null,"subExpense":""},
{"expensesKey":"movies","expensesValue":null,"subExpense":""},
{"expensesKey":"clothes","expensesValue":null,"subExpense":""}
],
}
},
methods: {
btnClickedHandler(e) {
console.log(e)
}
}
}
</script>

how to uncheck answered question in child vue from parent

My application is similar to a quiz. each question may have radio button answers, integers, text etc.
Consider the case for multiple choice question. I have a vue for the Radio button options. in the parent Vue i have a reset button for each of the question. If I click on the reset, it should removed the selected answer for that particular question.
How can I achieve this given that the reset button is in Parent vue and the answer to be reset is in the child Vue?
Parent:
<template>
<div class="inputContent">
<p class="lead" v-if="title">
{{title}}
<span v-if="valueConstraints.requiredValue" class="text-danger">* .
</span>
</p>
<b-alert variant="danger" show v-else>
This item does not have a title defined
</b-alert>
<!-- If type is radio -->
<div v-if="inputType==='radio'">
<Radio :constraints="valueConstraints" :init="init"
:index="index" v-on:valueChanged="sendData" />
</div>
<!-- If type is text -->
<div v-else-if="inputType==='text'">
<TextInput :constraints="valueConstraints" :init="init" v-
on:valueChanged="sendData"/>
</div>
<div class="row float-right">
<b-button class="" variant="default" type=reset #click="reset">
Reset1
</b-button>
<b-button class="" variant="default" v-
if="!valueConstraints.requiredValue" #click="skip">
Skip
</b-button>
</div>
</div>
</template>
<style></style>
<script>
import { bus } from '../main';
import Radio from './Inputs/Radio';
import TextInput from './Inputs/TextInput';
export default {
name: 'InputSelector',
props: ['inputType', 'title', 'index', 'valueConstraints',
'init'],
components: {
Radio,
TextInput,
},
data() {
return {
};
},
methods: {
skip() {
this.$emit('skip');
},
// this emits an event on the bus with optional 'data' param
reset() {
bus.$emit('resetChild', this.index);
this.$emit('dontKnow');
},
sendData(val) {
this.$emit('valueChanged', val);
this.$emit('next');
},
},
};
</script>
the child vue:
<template>
<div class="radioInput container ml-3 pl-3">
<div v-if="constraints.multipleChoice">
<b-alert show variant="warning">
Multiple Choice radio buttons are not implemented yet!
</b-alert>
</div>
<div v-else>
<b-form-group label="">
<b-form-radio-group v-model="selected"
:options="options"
v-bind:name="'q' + index"
stacked
class="text-left"
#change="sendData"
>
</b-form-radio-group>
</b-form-group>
</div>
</div>
</template>
<style scoped>
</style>
<script>
import _ from 'lodash';
import { bus } from '../../main';
export default {
name: 'radioInput',
props: ['constraints', 'init', 'index'],
data() {
return {
selected: null,
};
},
computed: {
options() {
return _.map(this.constraints['itemListElement'][0]['#list'], (v) => {
const activeValueChoices = _.filter(v['name'], ac => ac['#language'] === "en");
return {
text: activeValueChoices[0]['#value'],
value: v['value'][0]['#value'],
};
});
},
},
watch: {
init: {
handler() {
if (this.init) {
this.selected = this.init.value;
} else {
this.selected = false;
}
},
deep: true,
},
},
mounted() {
if (this.init) {
this.selected = this.init.value;
}
bus.$on('resetChild', this.resetChildMethod);
},
methods: {
sendData(val) {
this.$emit('valueChanged', val);
},
resetChildMethod(selectedIndex) {
this.selected = false;
},
},
};
</script>
One way would be to use an event bus
in your main js add:
//set up bus for communication
export const bus = new Vue();
in your parent vue:
import {bus} from 'pathto/main.js';
// in your 'reset()' method add:
// this emits an event on the bus with optional 'data' param
bus.$emit('resetChild', data);
in your child vue
import {bus} from 'path/to/main';
// listen for the event on the bus and run your method
mounted(){
bus.$on('resetChild', this.resetChildMethod());
},
methods: {
resetChildMethod(){
//put your reset logic here
}
}

Categories

Resources