Why does old value not passed to vue component? - javascript

why does old value not passed to vue component?
passed old value here
<autocomplete-region-component :query="old('regionName')"></autocomplete-region-component>
my code from vue component
<template>
<div>
<input
type="text"
autocomplete="off"
v-model="query"
v-on:keyup="autoComplete"
class="form-control js-region-name"
name="regionName"
value=""
>
<input
type="hidden"
class="form-control js-region-id"
name="regionId"
value="enteredRegion">
<div class="panel-footer" v-if="results.length">
<ul class="list-group select-region">
<li class="list-group-item list-region" v-for="result in results" v-on:click="selectRegion(result)">
{{ result.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data() {
return {
results: [],
query: '',
}
},
methods: {
autoComplete() {
this.results = [];
if(this.query.length > 2){
axios.get('/api/regions',{params: {_limit: 2, query: this.query}}).then(response => {
this.results = response.data;
});
}
},
selectRegion(result) {
let inputWithRegionName = document.querySelector('.js-region-name');
let inputWithRegionId = document.querySelector('.js-region-id');
let listRegions = document.querySelector('.panel-footer');
inputWithRegionName.value = result.name;
inputWithRegionId.value = result.id;
listRegions.hidden = true;
}
}
}
</script>
console not have mistakes
Please help with a detailed answer since I am a beginner and really need your help. thanks
UPDATE

When you create a component (like you have with <autocomplete-region-component>, if you want to pass values to it from it's parent, you have to define the prop at the component level. So in your script, add a props property like so:
props: [
'query',
],
Now in your autocomplete-region-component component, you can use the value of query as this.query as you would expect.
In your component tag, you don't use mustache tags to pass the value, you would just pass normal javascript. I'd also recommend not encoding the json at that point. You could always encode it within the component if you needed to.
<autocomplete-region-component :query="old('regionName')">
</autocomplete-region-component>

You have to define a prop and set the data to that prop
When you pass data in a component tag via the v-bind shorthand : these data are known as props and have to be defined as such
Vue disallows mutating props passed from the parent in the child, therefor you should make a local copy of that prop in the reactive data object
props: {
queryProp: {
required: false,
type: String
}
},
data() {
return {
results: [],
query: this.queryProp
};
},
Assuming a Blade view like so
<div id="app">
<form action="/" method="post">
#csrf
<input type="text" name="regionName"> <br>
<button type="submit">Submit</button>
</form>
<autocomplete-region-component :query-prop="{{ json_encode(old('regionName')) }}"></autocomplete-region-component>
</div>
<script src="/js/app.js"></script>
And routes like so
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::view('/', 'welcome');
Route::post('/', fn (Request $request) => $request->validate(['regionName' => 'integer']));
This is what you would get if invalid data is posted

Mustache syntax is to interpolate text, not to pass props.
To pass props or set attributes to child components (including native HTML elements), you need to use v-bind directive (doc).
<a> element is good to remember this rule.
<a :href="myUrl">{{ linkText }}</a>
Then, your code should look like this:
<autocomplete-region-component :query="json_encode(old('regionName'))">
</autocomplete-region-component>

Related

vue.js and slot in attribute

I'm trying to build a vue.js template that implements following:
<MyComponent></MyComponent> generates <div class="a"></div>
<MyComponent>b</MyComponent> generates <div class="a" data-text="b"></div>.
Is such a thing possible?
EDIT
Here is the best I can reach:
props: {
text: {
type: [Boolean, String],
default: false
}
},
and template
<template>
<div :class="classes()" :data-text="text">
<slot v-bind:text="text"></slot>
</div>
</template>
but the binding does not work, text always contains false.
You can use the mounted() method to get text using $slot.default property of the component to get the enclosing text. Create a text field in data and update inside mounted() method like this :
Vue.component('mycomponent', {
data: () => ({
text: ""
}),
template: '<div class="a" :data-text=text></div>',
mounted(){
let slot = this.$slots.default[0];
this.text=slot.text;
}
});
Note: It will only work for text, not for Html tags or components.
You're mixing slots and properties here. You'll have to pass whatever you want to end up as your data-text attribute as a prop to your component.
<MyComponent text="'b'"></MyComponent>
And in your template you can remove the slot
<template>
<div :class="classes()" :data-text="text"></div>
</template>
Another thing: it looks like your binding your classes via a method. This could be done via computed properties, take a look if you're not familiar.
You can try this.
<template>
<div :class="classes()">
<slot name="body" v-bind:text="text" v-if="hasDefaultSlot">
</slot>
</div>
</template>
computed: {
hasDefaultSlot() {
console.log(this)
return this.$scopedSlots.hasOwnProperty("body");
},
}
Calling
<MyComponent>
<template v-slot:body="props">
b
</template>
</MyComponent>

Array change detection for an array of complex objects in Vue JS 2

Update
Vue JS 3 will properly handle this: https://blog.cloudboost.io/reactivity-in-vue-js-2-vs-vue-js-3-dcdd0728dcdf
Problem:
I have a vue component that looks like this:
sub-comp.vue
<template>
<div>
<input type="text" class="form-control" v-model="textA">
<input type="text" class="form-control" v-model="textB">
<input type="text" class="form-control" v-model="textC">
</div>
</template>
<script>
export default {
props: {
textA: {
type: Number,
required: false
},
textB: {
type: Number,
required: false
},
textC: {
type: Number,
required: false
}
}
}
</script>
I have a parent component that looks like this:
layout-comp.vue
<template>
<div>
<button #click="addItem">Add</button>
<ul>
<li v-for="listItem in listItems"
:key="listItem.id">
<sub-comp
:textA="listItem.item.textA"
:textB="listItem.item.textB"
:textC="listItem.item.textC"
/>
</li>
</ul>
</div>
</template>
import subComp from '../sub-comp.vue'
export default {
components: {
subComp
},
data() {
return {
listItems: []
}
},
methods: {
addItem: function () {
var item = {
textA: 5,
textB: 100,
textC: 200
}
if (!item) {
return
}
this.length += 1;
this.listItems.push({
id: length++,
item: item
});
}
}
</script>
The thing is, anything I do to edit the textboxes, the array doesn't get changed, even though the reactive data shows that it changed. For example, it will always be as
{
textA: 5,
textB: 100,
textC: 200
}
Even if I changed textB: 333, the listItems array still shows textB: 100. This is because of this:
https://v2.vuejs.org/v2/guide/list.html#Caveats
Due to limitations in JavaScript, Vue cannot detect the following changes to an array
Question:
I'm wondering how do I update the array? I also want the change to occur when leaving the textbox, using the #blur event. I'd like to see what ways this can be done.
I read these materials:
https://codingexplained.com/coding/front-end/vue-js/array-change-detection
https://v2.vuejs.org/v2/guide/list.html
But it seems my example is a bit more complex, as it has indexes associated, and the arrays have complex objects.
Update 4/12/2018
Found out that in my addItem() that I had:
item = this.conditionItems[this.conditionItems.length - 1].item);
to
item = JSON.parse(JSON.stringify(this.conditionItems[this.conditionItems.length - 1].item));
I was thinking the sync modifier in the answer below was causing problems because it duplicated all items. But that's not the case. I was copying a vue object (including the observable properties), which caused it to happen. The JSON parse and JSON stringify methods only copies the properties as a normal object, without the observable properties. This was discussed here:
https://github.com/vuejs/Discussion/issues/292
The problem is that props flow in one direction, from parent to child.
Setting the value using v-model in child won't affect parent's data.
Vue has a shortcut to update parent's data more easily. It's called .sync modifier.
Here's how.
In sub-comp.vue
<template>
<div>
<input type="text" class="form-control" :value="textA" #input="$emit('update:textA', $event.target.value)" >
<input type="text" class="form-control" :value="textB" #input="$emit('update:textB', $event.target.value)">
<input type="text" class="form-control" :value="textC" #input="$emit('update:textC', $event.target.value)">
</div>
</template>
<script>
export default {
// remains the same
}
</script>
add .sync when you add the props
<sub-comp
:textA.sync="listItem.item.textA" // this will have the same effect of v-on:update:textA="listItem.item.textA = $event"
:textB.sync="listItem.item.textB"
:textC.sync="listItem.item.textC"
/>
update:
if you have reactivity problem, don't use .sync, add a custom event and use $set
<sub-comp
:textA="listItem.item.textA" v-on:update:textA="$set('listItem.item','textA', $event)"
/>

Using sync modifier between Parent and Grandchildren Vue 2

Problem
Let's say I have a vue component called:
Note: All vue components has been simplified to explain what I'm trying to do.
reusable-comp.vue
<template>
<div class="input-group input-group-sm">
<input type="text" :value.number="setValue" class="form-control" #input="$emit('update:setValue', $event.target.value)">
<span>
<button #click="incrementCounter()" :disabled="disabled" type="button" class="btn btn-outline-bordercolor btn-number" data-type="plus">
<i class="fa fa-plus gray7"></i>
</button>
</span>
</div>
</template>
<script>
import 'font-awesome/css/font-awesome.css';
export default {
props: {
setValue: {
type: Number,
required: false,
default: 0
}
},
data() {
return {
}
},
methods: {
incrementCounter: function () {
this.setValue += 1;
}
}
}
</script>
Then in a parent component I do something like this:
subform.vue
<div class="row mb-1">
<div class="col-md-6">
Increment Value of Num A
</div>
<div class="col-md-6">
<reuseable-comp :setValue.sync="numA"></reuseable-comp>
</div>
</div>
<script>
import reusableComp from '../reusable-comp'
export default {
components: {
reusableComp
},
props: {
numA: {
type: Number,
required: false,
default: 0
}
},
data() {
return {
}
}
</script>
then lastly
page_layout.vue
<template>
<div>
<subform :numA.sync="data1" />
</div>
</template>
<script>
import subform from '../subform.vue'
export default {
components: {
subform
},
data() {
return {
data1: 0
}
}
}
</script>
Question
So, how do I sync a value between reusable-comp.vue, subform.vue, and page_layout.vue
I'm using reuseable-comp.vue is many different places. I'm using subform.vue only a couple times in page_layout.vue
And I'm trying to use this pattern several times. But I can't seem to get this to work. The above gives me an 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: "numA"
Okay I found a solution that worked.
In subform.vue, we change:
data() {
return {
numA_data : this.numA
}
}
So we now have reactive data to work with. Then in the template, we refer to that reactive data instead of the prop:
<reuseable-comp :setValue.sync="numA_data"></reuseable-comp>
Then finally we add a watcher to check if the reactive data gets changed, and then emit to the parent:
watch: {
numA_data: function(val) {
this.$emit('update:numA', this.numA_data);
}
}
Now all values from grandchildren to parent are synced.
Update (4/13/2018)
I made new changes to the reusable-comp.vue:
I replaced where it says 'setValue' to 'value'
I replaced where it says 'update:value' to 'input'
Everything else says the same.
Then in subform.vue:
I replaced ':setValue.sync' to 'v-model'
v-model is two way binding, so I made use of that where it needed to be. The sync between the parent-child (not child to grandchild), is still using sync modifier, only because the parent has many props to pass. I could modify this where I could group up the props as a single object, and just pass that.

Input-fields as components with updating data on parent

I'm trying to make a set of components for repetitive use. The components I'm looking to create are various form fields like text, checkbox and so on.
I have all the data in data on my parent vue object, and want that to be the one truth also after the user changes values in those fields.
I know how to use props to pass the data to the component, and emits to pass them back up again. However I want to avoid having to write a new "method" in my parent object for every component I add.
<div class="vue-parent">
<vuefield-checkbox :vmodel="someObject.active" label="Some object active" #value-changed="valueChanged"></vuefield-checkbox>
</div>
My component is something like:
Vue.component('vuefield-checkbox',{
props: ['vmodel', 'label'],
data(){
return {
value: this.vmodel
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="value" #change="$emit('value-changed', value)">
{{label}}
</label>
</div>
</div>`
});
I have this Vue object:
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
},
methods: {
valueChange: function (newVal) {
this.carouselAd.autoOrder = newVal;
}
}
});
See this jsfiddle to see example: JsFiddle
The jsfiddle is a working example using a hard-coded method to set one specific value. I'd like to eighter write everything inline where i use the component, or write a generic method to update the parents data. Is this possible?
Minde
You can use v-model on your component.
When using v-model on a component, it will bind to the property value and it will update on input event.
HTML
<div class="vue-parent">
<vuefield-checkbox v-model="someObject.active" label="Some object active"></vuefield-checkbox>
<p>Parents someObject.active: {{someObject.active}}</p>
</div>
Javascript
Vue.component('vuefield-checkbox',{
props: ['value', 'label'],
data(){
return {
innerValue: this.value
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="innerValue" #change="$emit('input', innerValue)">
{{label}}
</label>
</div>
</div>`
});
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
}
});
Example fiddle: https://jsfiddle.net/hqb6ufwr/2/
As an addition to Gudradain answer - v-model field and event can be customized:
From here: https://v2.vuejs.org/v2/guide/components.html#Customizing-Component-v-model
By default, v-model on a component uses value as the prop and input as
the event, but some input types such as checkboxes and radio buttons
may want to use the value prop for a different purpose. Using the
model option can avoid the conflict in such cases:
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
// this allows using the `value` prop for a different purpose
value: String
},
// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
The above will be equivalent to:
<my-checkbox
:checked="foo"
#change="val => { foo = val }"
value="some value">
</my-checkbox>

Vue.js: correct way of changing a component prop from inside its method

I have the following code for a Vue.js component:
var list = Vue.component('list', {
props: ['items', 'headertext', 'placeholder'],
template: `
<div class="col s6">
<div class="search">
<searchbox v-bind:placeholder=placeholder></searchbox>
<ul class="collection with-header search-results">
<li class="collection-header"><p>{{ headertext }}</p></li>
<collection-item
v-for="item in items"
v-bind:texto="item.nome"
v-bind:link="item.link"
v-bind:key="item.id"
>
</collection-item>
</ul>
</div>
</div>`,
methods: {
atualizaBibliografiasUsandoHipotese: function (value) {
var query = gql`query {
allHipotese(nome: "${value}") {
id
nome
bibliografiasDestaque {
id
nome
link
descricao
}
}
}`;
client.query({ query }).then(function(results) {
this.items = results['data']['allHipotese'][0]['bibliografiasDestaque'];
}.bind(this));
},
}
});
And outside the component, in another javascript file, I call the atualizaBibliografiasUsandoHipotese method to make a query to the backend (GraphQL), and update the component. This works fine, however I get a warning message from Vue saying
[Vue warn]: 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: "items"
I made several attempts to fix this warning, but none of them worked. Anyone can help me on how can I fix this warning? Thanks
As mentioned, you should not mutate props, instead you can do this:
var list = Vue.component('list', {
props: ['items', 'headertext', 'placeholder'],
template: `
<div class="col s6">
<div class="search">
<searchbox v-bind:placeholder=placeholder></searchbox>
<ul class="collection with-header search-results">
<li class="collection-header"><p>{{ headertext }}</p></li>
<collection-item
v-for="item in myItems"
v-bind:texto="item.nome"
v-bind:link="item.link"
v-bind:key="item.id"
>
</collection-item>
</ul>
</div>
</div>`,
data: ()=> { return {
myItems: this.items
}
},
methods: {
atualizaBibliografiasUsandoHipotese: function (value) {
// Your Code
this.myItems = results['data']['allHipotese'][0]['bibliografiasDestaque'];
}.bind(this));
},
});
Notice that v-for is now looping over items in myItems.
This is what you are looking for:
https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
It even says:
This means you should not attempt to mutate a prop inside a child
component. If you do, Vue will warn you in the console.

Categories

Resources