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>
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.
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>
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">
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.