Vue: Pass parent data to child when updated with button click - javascript

I am new to Vue and trying to pass data from a parent component to the child when the parent data is updated via button click. The parent data is being updated, but the updated data is not being passed onto the child.
My main App.vue file is fairly standard:
<template>
<div id="app">
<parent></parent>
</div>
</template>
<script>
import parent from './components/parent.vue';
export default {
components: {
'parent':parent
},
name: 'app',
data () {
return {
}
}
}
</script>
The parent component has a button click to update the data and the change is displayed in the component
Parent.vue:
<template>
<div>
<h2>Text Input</h2>
<input v-model="input1"> {{ input1 }}
<h2>Multi Checkbox</h2>
<input type="checkbox" id="jack" value="Jack" v-model="input2">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="input2">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="input2">
<label for="mike">Mike</label>
<p>Checked names: <pre>{{ input2 }}</pre></p>
<h2>Radio</h2>
<input type="radio" id="one" value="One" v-model="input3">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="input3">
<label for="two">Two</label>
<br>
<span>Picked: {{ input3 }}</span>
<h2>new stuff</h2>
<button #click="getData()">Push Me</button>
<p> The data is changing here: {{ allInputs }} </p>
<child :all-inputs="allInputs"></child >
</div>
</template>
<script>
import child from './child .vue';
export default{
components: {
'child':child
},
data(){
return{
input1: '',
input2: [],
input3: '',
allInputs: ''
}
},
methods: {
getData(){
this.allInputs = {
input1: this.input1,
input2: this.input2,
input3: this.input3
}
}
}
}
</script>
But when I pass the data from the parent to a child, the data is not displayed on update:
Child.vue:
<template>
<div class="all-inputs">
<p>{{ passedData }}</p>
</div>
</template>
<script>
export default {
props: ["allInputs"],
data (){
return{
passedData: this.allInputs
}
}
}
</script>

Your child is not updating because you are using the local data property passedData in the template, not the props allInputs. If you will console the allInputs somewhere in the child component you will see that it is updating whenever the parent updates but as you are not using the prop directly so you are not seeing its reactivity. So, use props in your template instead of local data property, like this-
<p>{{ allInputs }}</p>
Not this-
<p>{{ passedData }}</p>
The approach you are trying to do is used when the child component wants to update the props passed by the parent but mutating parent props is erroneous because a child should not mutate (modify) the parent's state. So we usually assign props to a local data property and then mutate that data property, not props (same as you are doing). You can read more about this concept here.

Instead of having passedData in your template like this:
<template>
<div class="all-inputs">
<p>{{ passedData }}</p>
</div>
</template>
Change it to:
<template>
<div class="all-inputs">
<p>{{ allInputs }}</p>
</div>
</template>

Related

Use parent slot variables in Vue template slot

I have a simple FormComponent:
<template>
<form>
<fieldset>
<slot />
</fieldset>
<span v-if="!isEditing" #click="edit()">Edit</span>
</form>
</template>
<script>
export default {
data () {
return {
isEditing: false,
}
},
methods: {
edit (state) {
this.isEditing = !this.isEditing
}
}
}
</script>
And when I use the component:
<FormComponent>
<input value="Man" type="text" :disabled="!isEditing">
</FormComponent>
The input field are correctly slotted into the component but the :disabled="!isEditing" from the slot isn't reacting to the change of isEditing in the FormComponent.
The Vue documentation is pretty good, but it doesn't cover each edge case.
The component with the <slot></slot> tag has to bind the data onto an attribute on the <slot> tag, like a prop:
<slot :isEditing="isEditing"></slot>
Then when you render that slotted component, Vue creates and exposes an object containing all of the bound data, with each attribute having a property on that object.
Access the object by adding an expression to the v-slot directive in this format:
<FormComponent v-slot:default="slotProps">
(Or use the alias # as in #default="slotProps".) You can access individual properties like isEditing via that object, like slotProps.isEditing:
<FormComponent #default="slotProps">
<input value="Man" type="text" :disabled="!slotProps.isEditing">
</FormComponent>
Here's the more common (and versatile) <template> syntax:
<FormComponent>
<template #default="slotProps">
<input value="Man" type="text" :disabled="!slotProps.isEditing">
</template>
</FormComponent>
You can also destructure slotProps for more direct access to the properties:
<FormComponent>
<template #default="{ isEditing }">
<input value="Man" type="text" :disabled="!isEditing">
</template>
</FormComponent>

Child of data object not being evaluated until logged. Vue.js

I am writing a component (form) that acts as a container for form fields that are passed from the parent component.
I pass the form fields to the child as a prop:
formFields: [
{ name: 'email', type: 'email' },
{
name: 'sub_group',
type: 'group',
contents: [
{ name: 'sub_field_01', type: 'text' },
{ name: 'sub_field_02', type: 'text' },
]
}
]
That is all displayed fine and works well in my child component.
I used a computed property to build up data to post to a route to update the resource. I group the sub_group so that when I am posting, the controller understands that the sub_group is an array.
However, the sub_group array is always empty until I console log it, so it only evaluates when logged. The top level (email in this case) always shows up.
I have tried getting rid of the computed property and use a method to build up the object to post but I have the same issue. I really have no idea what is happening. How can 'force' the computed property to evaluate?
EDIT Adding form example
Code that renders the form:
<div v-for="field in formFields">
<!-- Individual Field -->
<div v-if="field.type !== 'group'">
<label v-if="field.label">{{ field.label }}</label>
<input :type="field.type" :name="field.name" v-model="field.value">
</div>
<!-- Group -->
<div v-else>
<div v-for="child in field.group">
<label v-if="child.label">{{ child.label }}</label>
<input :type="child.type" :name="child.name" v-model="child.value">
</div>
</div>
</div>
Computed property for post:
<div v-for="field in formFields">
<!-- Individual Field -->
<div v-if="field.type !== 'group'">
<label v-if="field.label">{{ field.label }}</label>
<input :type="field.type" :name="field.name" v-model="field.value">
</div>
<!-- Group -->
<div v-else>
<div v-for="child in field.group">
<label v-if="child.label">{{ child.label }}</label>
<input :type="child.type" :name="child.name" v-model="child.value">
</div>
</div>
</div>
I realise this needs refactoring but I can't actually get it working like this. I have tried adding cache:false and a getter to the computed property but that didn't work.

Vue - passing v-model from child to parent in v-for loop

I'm building a form builder in Vue which allows users to add/remove various types of input. My method so far is to have a template component for each input type, then when a user selects that input type I push it into an array on the parent component to loop over and display.
However, I also need to pass the value of the input up to the parent (and store it in the input type="hidden" element), so I'm emitting an event in the child component and catching it in the parent. My problem is that the value of labelText gets updated identically for every input type="hidden" at once when I type, rather than individually. I can't work out what I'm missing?
Text input template
<template>
<div id="TextInput">
<input type="text" placeholder="Question" class="form__input_label" #input="sendLabelUp($event.target.value)" />
<input type="text" placeholder="Test placeholder" />
<slot name="removeField"></slot>
<slot name="hiddenInputs"></slot>
</div>
</template>
<script>
export default {
name: 'TextInput',
methods: {
sendLabelUp: function(val) {
this.$emit('input', val);
},
},
};
</script>
Parent template (just including the part I think is relevant)
<transition-group name="form__input_list" tag="div">
<component :is="input.type" v-for="(input, index) in inputs" v-bind="input" :key="input" v-model="labelText" class="form__input">
<div slot="removeField">
<a class="btn btn--tertiary sml-push-top-half" #click="removeField(index)">Remove</a>
</div>
<div slot="hiddenInputs">
<!-- Hidden inputs used to store question config -->
<input type="hidden" :name="`pages[0]questions[${index}]type[${input.type}]label[${labelText}]`" />
</div>
</component>
</transition-group>
<script>
export default {
name: 'InputGenerator',
components: {
TextInput,
TextArea,
NumberInput,
LikertScale,
},
data() {
return {
inputs: [],
dropdownActive: false,
labelText: '',
};
},
};
</script>

VueJs2 checkbox v-bind:true-value is not working

I have created a vuejs2 app using vue-cli. I'm trying to bind dynamic value for checkbox as vuejs documentation said: value binding. But its giving me undefined. If I don't bind vlaue its giving me true or false. This is my ValueBinding.vue component.
<template>
<div id="input">
<p> Selected value for smoking: {{ smoking }} </p>
<input v-model="smoking" v-bind:true-value="Y" v-bind:false-value="N" type="checkbox">
<label>No Smoking</label>
<br>
<button #click="submit">Submit</button>
</div>
</template>
<script>
export default {
name: 'value-binding',
data() {
return {
smoking: ''
}
},
methods: {
submit() {
console.log(this.smoking) //shows undefined
}
}
}
</script>
I'm new to vuejs. Thanks in advance.
When you use v-bind, it dynamically bind one or more attributes to an expression. In your case when you do
v-bind:true-value="Y"
It will try to find a data attribute: Y in vue instance, as you have not defined any such attribute, it will become undefined.
If you just want to true-value as "Y" and false-value as "N", do follwing:
<input v-model="smoking" true-value="Y" false-value="N" type="checkbox">

How to use Refs Properly in React js?

I am confused with how the react refs works.
The issue for me is, whenever I change the input select value, update_cart function is called.
I then call actions to set the value using relevant APIs.
However, currently, whenever I change the value, the whole component refreshes and the refs value are set to undefined.
What am I doing wrong?
Please note I have included only relevant codes.
/** #jsx React.DOM */
'use strict'
var React = require('react')
var connect = require("react-redux").connect
var moment = require('moment')
var actions=require("../actions");
var Calendar=require("./calendar");
var utils=require("../utils");
var CartChangeQty = require('./cart_change_qty')
var Table = require('react-bootstrap').Table
var Input = require('react-bootstrap').Input
var Register = require('./register')
var GroceryCheckout = React.createClass({
getInitialState: function() {
return {provinces: [], postal_codes: []};
},
render: function() {
console.log("GroceryCheckout.render");
var day_slots=[];
if (this.props.grocery_cart) {
var delivery_date=this.props.grocery_cart.delivery_date;
if (!delivery_date) delivery_date=this.props.grocery_cart.delivery_slots[0][0];
_.each(this.props.grocery_cart.delivery_slots,function(obj) {
if (obj[0]==delivery_date) {
day_slots=obj[1];
}
}.bind(this));
console.log("###### day_slots",day_slots);
}
return <div className="plr-grocery-checkout">
<a className="plr-anchor" id="checkout"></a>
<h2>Grocery Checkout</h2>
{function() {
if (!this.props.grocery_cart) return <p>Your grocery cart is empty.</p>;
if (!this.props.user_data) {
return <div>
<p>
Is this your first time ordering? <input type="radio" name="first_time" ref="first_time_yes" onClick={this.onchange_first_time.bind(this,true)}/> Yes <input type="radio" name="first_time" ref="first_time_no" onClick={this.onchange_first_time.bind(this,false)}/> No
</p>
{function() {
if (this.state.first_time==true) {
return <Register/>
} else if (this.state.first_time==false) {
return ///something
} else {
return <div>
///something
<div>
<h4><i className="glyphicon glyphicon-home"> </i> Delivery Address</h4>
<Input type="select" onChange={this.update_cart} ref="ship_address" style={{width:'auto',padding:'inherit',height:'auto'}}>
{this.props.user_data.contact_id.addresses.map(function(obj) {
return <option key={obj.id} value={obj.id}>{obj.address}</option>
})}
</Input>
<h4><i className="glyphicon glyphicon-calendar "> </i> Please select your preferred delivery time slot:</h4>
<Calendar />
<div className="form-group">
<label className="col-sm-2 control-label">Payment Method</label>
<div className="col-sm-6">
<Input type="select" onChange={this.update_cart} ref="pay_method" style={{width:'auto',padding:'inherit',height:'auto'}}>
{this.props.grocery_cart.payment_methods.map(function(obj) {
console.log("********************** payment method",obj.name)
return <option key={obj.id} value={obj.id}>{obj.name}</option>
}.bind(this))}
</Input>
</div>
</div>
<div className="form-group">
<label className="col-sm-2 control-label">Payment Amount</label>
<div className="col-sm-6">
<p>{this.props.grocery_cart.amount_total} ฿</p>
</div>
</div>
</div>
<hr/>
<h4><i className=" glyphicon glyphicon-list-alt"> </i> Other Information</h4>
<div className="form-horizontal">
<div className="form-group">
<label className="col-sm-2 control-label">Return Foam Box</label>
<div className="col-sm-6">
<input type="checkbox" onChange={this.update_cart}/>
<span style={{margin:10}}>For this delivery, would you like us to take back the foam box for recycling?</span>
</div>
</div>
</div>
<div className="form-horizontal">
<div className="form-group">
<label className="col-sm-2 control-label">No Call</label>
<div className="col-sm-6">
<input type="checkbox" onChange={this.update_cart}/>
<span style={{margin:10}}>For this delivery, please do NOT call me one hour before delivery to re-confirm unless delayed</span>
</div>
</div>
</div>
<button className="btn btn-lg btn-primary" onClick={this.send_order} disabled={this.props.grocery_cart.amount_total<1500?true:false}><span className="glyphicon glyphicon-ok"></span> Send Order</button>
{this.props.sending_grocery_order?<span><img src="/static/img/spinner.gif"/> Sending order...</span>:null}
{function() {
if (this.props.grocery_cart.amount_total>=1500) return;
return <span className="plr-warning" style={{marginLeft:"20px"}}>Min. order: 1500 ฿!</span>
}.bind(this)()}
</div>
}
}.bind(this)()}
</div>
},
onchange_first_time: function(value) {
console.log("GroceryCheckout.onchange_first_time",value);
this.setState({first_time: value});
},
update_cart: function() {
console.log("GroceryCheckout.update_cart");
console.log("this.refs.pay_method.value",this.refs.pay_method.value);
var vals={
customer_id: this.props.user_data.contact_id.id,
ship_address_id: this.refs.ship_address.value||null,
bill_address_id: this.refs.bill_address.value||null,
pay_method_id: parseInt(this.refs.pay_method.value),
};
this.props.dispatch(actions.grocery_cart_update(vals));
},
onchange_qty: function(product_id,qty) {
console.log("GroceryItem.onchange_qty",product_id,qty);
this.props.dispatch(actions.grocery_cart_set_qty(product_id,qty));
},
})
var select=function(state) {
return {
grocery_cart: state.grocery_cart,
grocery_cart_loading: state.grocery_cart_loading,
user_data: state.user_data,
user_data_loading: state.user_data_loading,
sending_grocery_order: state.sending_grocery_order,
}
}
module.exports=connect(select)(GroceryCheckout);
The reason your setup breaks down is probably because:
on_change_first_time() includes a setState(), which re-renders your component.
update_cart() dispatches an action. Probably this action triggers a new set of props for the component, causing the component to be re-rendered.
In both cases, your refs are probably preserved, but the values are not. Because they are not part of props, nor state. Because the props and state do not include value, react will empty the values upon re-rendering.
It is generally not good practice to read values from input components usings this.refs. In React, refs are for reading and updating DOM, but intended to be used only for stuff that you cannot do through pure react. Examples would be to read the height or width of HTMl components, or to add or remove event listeners to DOM components.
In your case, your update_cart() sends all values to some sort of other function, which presumably stores them somewhere.
I would advise:
put all values of all inputs in props, and pass them to the component.
in your render function, give all your input components a value={this.props.foo} value or similar.
That way, after your cart is sent of and updated, the component will be re-rendered with the new values.
Optionally, you could include optimistic rendering, by adding:
in getInitialState(), copy all your prop values to state values (as initial state of the component).
include parameters in input fields like value={this.state.foo}
in update_cart(), after your dispatch, add a setState() to update the state to the new input values.
The input i am using is a react bootstrap
<Input type="select" onChange={this.update_cart} ref="ship_address" style={{width:'auto',padding:'inherit',height:'auto'}}>
{this.props.user_data.contact_id.addresses.map(function(obj) {
return <option key={obj.id} value={obj.id}>{obj.address}</option>
})}
</Input>
The problem was whenever the onchange was called the render method was again called.
It rerenders because i am calling the set state in here.
One way to fix the issue is to put the input into a different component , where it just re renders that component on change .
Cheers.

Categories