I'm despaired. I absolutely have no clue anymore how to outsource html "select"-tag to a single Vue component file. I tried so much different ways but unfortunately I've never got it to work yet.
User component:
<custom-select :id="'continent'" :selected="user.continent" :options="continents" #change.native="changedContinent" class="some css classes" />
export default {
props: ['user'],
components: {
CustomSelect,
},
data() {
return {
continents: [
{ value: 'africa', text: 'Africa' },
{ value: 'america-north', text: 'America (North)' },
{ value: 'america-south', text: 'America (South)' },
{ value: 'asia', text: 'Asia' },
{ value: 'australia', text: 'Australia' },
{ value: 'europe', text: 'Europe' }
]
};
},
methods: {
updateUser() {
axios.post('/updateUser', this.user).then(() => {});
},
changedContinent() {
// Do something with the new selected continent like changing background relative to the selected continent
}
}
};
CustomSelect:
<template>
<select v-model="selected" class="some css classes">
<option v-for="option in options" :key="option.value" :value="option.value">{{ option.text }}</option>
</select>
</template>
<script>
export default { props: ['id', 'selected', 'label', 'options'] };
</script>
Whenever I change the selected value it should update user.continent value but instead, it throws the following error:
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders. Instead, use a data or
computed property based on the prop's value. Prop being mutated:
"selected"
But I do not know how to handle this.
Could anyone help me please?Thank you in advance!
As the error message says, 'to avoid mutating a prop directly, use a data or computed property ...'
I modified your code to create the components in my test environment (Vue 2 using Vue CLI), and bound the CustomSelect to a new data property 'selectedContinent' initialized from the 'selected' prop. I am also updating the parent component (User component in your case) via a custom event. Here are my components. See the Vue Custom Events documentation
My CustomSelect.vue component:
<template>
<div class="custom-select">
<select v-model="selectedContinent" class="" v-on:change="changeContinent">
<option v-for="option in options" :key="option.value" :value="option.value">{{ option.text }}</option>
</select>
</div>
</template>
<script>
export default {
props: ['id', 'selected', 'label', 'options'],
data() {
return {
selectedContinent: this.selected
}
},
methods: {
changeContinent() {
//console.log('changeContinent: ' + this.selectedContinent);
this.$emit('change-continent-event', this.selectedContinent);
}
}
}
</script>
My Parent.vue component (your User):
<template>
<div class="parent">
<custom-select :id="'continent'" :selected="user.continent" :options="continents"
v-on:change-continent-event="changedContinent" />
</div>
</template>
<script>
import CustomSelect from './CustomSelect.vue'
export default {
props: ['user'],
components: {
CustomSelect,
},
data() {
return {
continents: [
{ value: 'africa', text: 'Africa' },
{ value: 'america-north', text: 'America (North)' },
{ value: 'america-south', text: 'America (South)' },
{ value: 'asia', text: 'Asia' },
{ value: 'australia', text: 'Australia' },
{ value: 'europe', text: 'Europe' }
]
};
},
methods: {
updateUser() {
//axios.post('/updateUser', this.user).then(() => { });
},
changedContinent(newContinent) {
// Do something with the new selected continent like changing background relative to the selected continent
console.log('changedContinent: ' + newContinent);
}
}
}
</script>
Related
I am migrating things from Vue.js 2 to Vue.js 3. During my migration, I just mentioned that eslint does warn me, because I am mutating props.
This is an example of an element that causes this:
<template>
<ToggleField
v-model="item.checked"
:name="`item.${name}.checked`"/>
</template>
<script>
import ToggleField from "./ToggleField";
export default {
name: 'TestField',
components: {ToggleField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
This element is deeply nested and every parent element passes the :item-attribute to the next "level" until it's finally displayed and changeable due v-model.
This is an example:
Parent view
<template>
<CustomForm name="test" :item="item" />
<!-- Reflect changes on item here -->
{{ item }}
</template>
<script>
import CustomForm from "./CustomForm";
export default {
components: {
CustomForm
},
data: () => ({
item:
{name: 'Foo', 'checked': false},
}),
}
</script>
CustomForm
<template>
<!-- Do other fancy stuff here -->
<TestField :name="name" :item="item"/>
</template>
<script>
import TestField from "./TestField";
export default {
name: 'CustomForm',
components: {TestField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
TestField
<template>
<ToggleField
v-model="item.checked"
:name="`item.${name}.checked`"/>
</template>
<script>
import ToggleField from "./ToggleField";
export default {
name: 'TestField',
components: {ToggleField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
So my question is: How can I update the item and reflect the changes to it's parent (and it's parent, and it's parent again, if necessary) without running into the prop-mutation?
I am developing a Vue app with pimcore and twig in the backend. I have to create a component that receives the slot (another component), and render it inside, but with dynamic props.
Here is root in viani.twig.html:
<div>
<viani-accordion>
<viani-filter-checkbox v-slot="{filterRows}"></viani-filter-checkbox>
</viani-accordion>
</div>
There is nothing special. viani-accordion is a parent component and the viani-filter-checkbox is a slot, which I have to render with appropriate props.
Here you can see the VianiAccordion.vue:
<template>
<div class="wrapper">
<AccordionTitle v-for="(item, index) in dataToRender" :item="item" :key="index">
/*I think this is incorrect, but I'm trying to prop data that I need in viani-filter-checkbox*/
<slot :filter-rows="item.items"></slot>
</AccordionTitle>
</div>
</template>
<script>
import AccordionTitle from './Accordion-Title';
export default {
name: "Viani-Accordion",
components: {AccordionTitle},
data() {
return {
dataToRender: [
{
name: 'first item',
items: [
{
name: 'oil olive',
quantity: 10,
},
{
name: 'subitem 2',
quantity: 11,
},
]
},
{
name: 'second item',
items: [
{
name: 'subitem 1',
quantity: 10,
},
{
name: 'subitem 2',
quantity: 11,
},
]
}
]
}
},
}
</script>
Then I have another deeper child component Accordion-Title.vue that is responsible for rendering the slot (so I have to pass the slot through the multiple components):
<template>
<div v-if="isOpen" class="child-wrapper">
/*I think this is incorrect, but I'm trying to prop data that I need in viani-filter-checkbox*/
<slot :filterRows="item.items"></slot>
</div>
</template>
<script>
export default {
name: "Accordion-Title",
props: {
item: {
type: Object,
default: null
}
}
}
</script>
and finally Viani-FiltersCheckbox.vue:
<template>
<div>
//child component which we don't need in this case
<FilterCheckboxRow v-for="(item, index) in filterRows" :item="item" :key="index"/>
</div>
</template>
<script>
import FilterCheckboxRow from './FilterCheckboxRow'
export default {
name: "VianiFilterCheckbox",
components: {
FilterCheckboxRow
},
props: {
//here I expect to get array to render, but got default value (empty array)
filterRows: {
type: Array,
default: function () {
return []
}
},
},
}
</script>
So I need to pass the props (filterRows) to the component (Viani-FiltersCheckbox.vue), which is rendered as a slot. I have read this and this, but still don't get where the mistake and how to get the props I need.
It looks like you're trying to access your props through props.XXX. That's typically only done in templates for functional components. Otherwise, the props would be accessed without the props. prefix (i.e., props.item.items should be item.items).
And to pass filterRows from the scope data to the child component, declare a <template>, and then move your child into that, binding filterRows there:
<viani-accordion>
<!-- BEFORE: -->
<!-- <viani-filter-checkbox v-slot="{filterRows}"></viani-filter-checkbox> -->
<template v-slot="{filterRows}">
<viani-filter-checkbox :filterRows="filterRows"></viani-filter-checkbox>
</template>
</viani-accordion>
I currently set up a new playground with VueJS/Laravel/Spark and want to implement a tags input component.
I don't understand how to register those components correctly. I'm following the how-to-guides and official documentation, but the implementation just works so-so.
I want to implement the library from #johmun -> http://www.vue-tags-input.com which I installed via npm (npm install #johmun/vue-tags-input).
I created a single file component named VueTagsInput.vue that looks like this:
<template>
<div>
<vue-tags-input
v-model="tag"
:tags="tags"
#tags-changed="newTags => tags = newTags"
:autocomplete-items="filteredItems"
/>
</div>
</template>
<script>
import VueTagsInput from '#johmun/vue-tags-input';
export default {
components: {
VueTagsInput,
},
data() {
return {
tag: '',
tags: [],
autocompleteItems: [{
text: 'Spain',
}, {
text: 'France',
}, {
text: 'USA',
}, {
text: 'Germany',
}, {
text: 'China',
}],
};
},
computed: {
filteredItems() {
return this.autocompleteItems.filter(i => {
return i.text.toLowerCase().indexOf(this.tag.toLowerCase()) !== -1;
});
},
},
};
</script>
I imported this single file component at resources/js/bootstrap.js like so:
import VueTagsInput from './VueTagsInput.vue'
And I'm using this component in the home.blade.php view like this:
<vue-tags-input v-model="tag"
autocomplete-always-open
add-from-paste
allow-edit-tags>
</vue-tags-input>
This renders an input with which I can interact as desired, but I can not use the autocomplete function with the countries entered above, and the console also throws the following error:
[Vue warn]: Property or method "tag" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
I don't know what I'm doing wrong.
So I stumbled across the solution by trial & error.
First I had to register the component the right way in resources/js/bootstrap.js like so:
import VueTagsInput from './VueTagsInput.vue'
Vue.component('vue-tags-input', VueTagsInput);
But this caused another error because I called the component within the component registration itself. I used the name option in the single file component in order to overcome this error. I gave my newly created component a different name like this:
<template>
<div>
<johmun-vue-tags-input
v-model="tag"
:tags="tags"
#tags-changed="newTags => tags = newTags"
:autocomplete-items="filteredItems"
/>
</div>
</template>
<script>
import JohmunVueTagsInput from '#johmun/vue-tags-input';
export default {
name: "VueTagsInput",
components: {
JohmunVueTagsInput,
},
data() {
return {
tag: '',
tags: [],
autocompleteItems: [{
text: 'Spain',
}, {
text: 'France',
}, {
text: 'USA',
}, {
text: 'Germany',
}, {
text: 'China',
}],
};
},
computed: {
filteredItems() {
return this.autocompleteItems.filter(i => {
return i.text.toLowerCase().indexOf(this.tag.toLowerCase()) !== -1;
});
},
},
};
</script>
I have 2 components, one creates an Editor using vue2-ace-editor, the other component is a select Option component. I Need to Change the theme of the Editor if I select another value in my select Option, so far my Editor component Looks like this:
<template>
<editor v-model="content" #init="editorInit" lang="javascript" theme="monokai"></editor> <!-- the default theme is monokai currently -->
</template>
<script>
export default {
props: [],
data () {
return {
language: 'javascript',
content: 'test',
}
},
components: {
editor: require('vue2-ace-editor'),
},
methods: {
editorInit () {
require('brace/ext/language_tools');
require('brace/mode/html');
require('brace/mode/javascript');
require('brace/mode/less');
require('brace/theme/clouds_midnight');
require('brace/theme/chrome');
require('brace/theme/ambiance');
}
},
}
</script>
And my select Option Looks like this:
<template>
<div>
<v-select
:items="themes"
v-model="themeSelection"
label="Select"
single-line
></v-select>
</div>
</template>
<script>
export default {
data () {
return {
themeSelection: null,
themes: [
{ text: 'ambiance' },
{ text: 'chrome' },
{ text: 'clouds_midnight' },
]
}
}
}
</script>
So basically if I select a different Option, I Need to Change the theme of the Editor, how do I manage this?
I wanted to add some own scenarios for new components by forking vue-play.
I'm having problems in more complicated cases of vue-select, particularly Two-Way Value Syncing.
Going into this scenario ends up with warning:
vue.esm.js:571 [Vue warn]: Property or method "syncedVal" is not
defined on the instance but referenced during render.
and no option in the dropdown is preselected either. I'm failing to understand why I keep getting this warning, despite defining syncedVal in component's props.
I've added two files into vue-play/play:
VSelect.vue:
<template>
<v-select v-model="selected" :options="options"></v-select>
</template>
<script>
import Vue from 'vue'
import vSelect from 'vue-select'
Vue.component('v-select', vSelect);
export default {
props: {
options: {
default: function() { return ['one', 'two'] },
type: Array
},
onchangeCallback: {
default: () => () => null
},
// doesn't seem to work:
syncedVal: {
default: 'one',
type: String
}
},
data() {
return {
selected: null
}
}
}
</script>
and VSelect.play.js:
import {play} from '../src/play'
import VSelect from './VSelect.vue'
play(VSelect)
.name('VSelect')
.displayName('VSelect')
.add('default', '<v-select />')
.add('multiple', '<v-select multiple />')
.add('custom options', `<v-select :options="['custom1','custom2']" />`)
.add('custom options with labels', `<v-select :options='[{value: "CA", label: "Canada"}, {value: "UK", label: "United Kingdom"}]' />`)
.add('2-way value sync', `<v-select :value.sync="syncedVal" />`) // doesn't seem to work
please note that the v-select component in VSelect.play.js is VSelect.vue
so there are some mistakes:
.add('multiple', '<v-select multiple />')
VSelect.vue do not have multiple props, so this multiple will not work as you expectd
FIX: add props to your component, and bind it to v-select
.add('2-way value sync', )
you define syncedVal in component's props, BUT you use it on other component (vue-play's component), they have different scope!
FIX: to use vue-play to demo this functionality, you need to write a full component, so you can have data to bind(see below for example code)
VSelect.vue do not implement sync (https://v2.vuejs.org/v2/guide/components.html#sync-Modifier), so nothing will happen here
I make an example from your component and play, I hope this will help you :)
Demo of my vue-play: https://vue-play-select.netlify.com/
Code: https://github.com/iampaul83/vue-play-select
here is how I fix them:
SelectFramework.vue:
<template>
<v-select
v-model="selected"
:options="options"
:multiple="multiple">
</v-select>
</template>
<script>
export default {
name: "SelectFramework",
props: {
options: {
default: () => ["Vue.js", "React", "Angular"],
type: Array
},
value: String, // to support v-model
foo: String, // to support :foo.sync
multiple: false // to support multiple select
},
data() {
return {
selected: this.value,
};
},
watch: {
selected () {
this.$emit('input', this.selected) // update v-model
this.$emit('update:foo', this.selected) // update foo.sync
},
value () {
this.selected = this.value // update from v-model
},
foo () {
this.selected = this.foo // update from foo.sync
}
}
};
</script>
play/index.js:
import { play } from 'vue-play'
import Vue from 'vue'
import vSelect from 'vue-select'
Vue.component('v-select', vSelect)
import SelectFramework from '../src/SelectFramework.vue'
play(SelectFramework)
.name('SelectFramework')
.displayName('Select Framework')
.add('default', '<select-framework />')
.add('multiple', '<select-framework multiple />')
.add('custom options', `<select-framework :options="['custom1','custom2']" />`)
.add('custom options with labels', `<select-framework :options='[{value: "CA", label: "Canada"}, {value: "UK", label: "United Kingdom"}]' />`)
// full component to demo v-model and :foo.sync
.add('v-model', {
data() {
return {
selected: null,
syncedVal: null
}
},
template: `
<div>
<p>selected: {{selected}} </p>
<p>syncedVal: {{syncedVal}} </p>
<select-framework
v-model="selected"
:foo.sync="syncedVal">
</select-framework>
<p>
<button #click="selected = 'Vue.js'">Set selected to Vue.js</button>
<button #click="syncedVal = 'React'">Set syncedVal to React</button>
</p>
</div>
`
})
// .add('2-way value sync', `<select-framework :value.sync="syncedVal" />`) // doesn't seem to work