I can not really access my dynamically generated children in a component. I found some cases on the internet, it seems like a common problem but nothing i could properly relate to or it was not really well documented so i got more confused.
I have a form which has a button and every time a press the button, subform is added to the form.
render: function() {
var prop_forms = [];
for(var i = 1; i <= this.state.count; i++){
prop_forms.push(<App.Form.Property reference={i}/>);
}
return(
<div>
<h2>New model</h2>
<form className="form" callback={this.submited}>
<label>Name:</label>
<input type="text" ref="name" className="fancy_input" required/>
<label><input type="checkbox" ref="include_endpoints"/> Include basic endpoints</label>
<h3>Properties <a onClick={this.add_property}><span className="glyphicon glyphicon-plus"></span></a></h3>
{prop_forms}
<button className="btn" onClick={this.submited} type="button">Add model to the canvas</button>
</form>
</div>
);
}
every time a click a button, this.state.count is incremented so I get one more App.Form.Property component. I am passing the i variable so i can use it in my ref parameter in the App.Form.Property component.
render: function(){
var input_ref = "property_"+this.props.reference+"_input";
var select_ref = "property_"+this.props.reference+"_select";
return(
<div className="property_form">
<input className="fancy_input" ref={input_ref} type="text" placeholder="Property name..."/>
<select className="fancy_select" ref={select_ref}>
<option value="string">String</option>
<option value="integer">Integer</option>
<option value="double">Double</option>
</select>
</div>
);
}
My problem is that when i submit the form, I can not access the children via ref. I can only address name and include_endpoints but nothing from the App.Form.Property component.
Add a function getSubmitData in your form component that encapsulates the children from the outer element
Related
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.
I have the next problem: I'm trying to use a modal jQuery form, this one, inside, changes dynamically the content of a table by using a template loaded. This template has a select which I want to load with data in the moment I put into the document.
This is the modal form:
<div id="divNuevoPartido" title="Nuevo Partido a introducir" style="display:none;">
<div id="divJugadoresDelPartido">
<h3>Introduce número de jugadores</h3>
<select id="selectJugadoresDelPartido">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<table id="tableJugadoresDelPartido"></table>
</div>
</div>
When 'selectJugadoresDelPartido' change, the table 'tableJugadoresDelPartido' changes dynamically depending of the number, insert this template inside the table n-times selected in 'selectJugadoresDelPartido'
This is the template, just fo 1 row:
<td>
<label for="jugadorName">Jugador</label>
<select class="selectorJugadores" name="jugadorName"></select>
<label for="isWinner">Ganador?</label>
<input type="checkbox" name="isWinner" />
<label for="isMvp">MVP?</label>
<input type="radio" name="isMvp" />
<label for="puntajeJugador">Puntaje</label>
<input type="number" name="puntajeJugador" />
<label for="golesJugador">Goles</label>
<input type="number" name="golesJugador" />
<label for="asistenciasJugador">Asistencias</label>
<input type="number" name="asistenciasJugador" />
<label for="salvadasJugador">Salvadas</label>
<input type="number" name="salvadasJugador" />
<label for="tirosJugador">Disparos</label>
<input type="number" name="tirosJugador" />
</td>
And finally, here is de JS code it should insert the template n-times selected above and also load data inside the select '.selectorJugadores'
function refrescarTablaJugadoresPartido(numJugadores){
$("#tableJugadoresDelPartido").empty();
for (var iMax = 0; iMax < parseInt(numJugadores); iMax++) {
var tmpRow = $("<tr></tr>");
tmpRow.attr("id", iMax);
tmpRow.load("views/templates/estadisticas_jugador_partido.html");
if($.isEmptyObject(usersList))
cargarJugadores();
var tmpOption = $("<option></option>");
tmpOption.html(usersList[0].getCodigo());
tmpRow.find("select").html(tmpOption);
$("#tableJugadoresDelPartido").append(tmpRow);
}
};
Everything inside the mehtod it's working but at the time I try to find the select into tmpRow, I seems it doesn't find and nothing is loaded inside.
NOTES:
usersList is a global variable: var usersList = new Array() with an objet with two variables: codigo and nombre, working fine in that way;
I'm loading just to test with one option, I'noticed about it :)
cargarJugadores() it's a function which load userList array (it's working perfecly)
Any ideas?
Thanks for everything
May be the document has not been loaded at the time you call the find command, beacuse load is asynchoronous.
You could put the find inside a callback in load, this way:
tmpRow.load("views/templates/estadisticas_jugador_partido.html" , function() {
tmpRow.find("select").html(tmpOption);
});
This way, the find command will be called once the load is complete.
Hi so my React component renders in the html and works with one catch.
The Catch is "data-reactid=".0.0"" this breaks the input and prevents you from being able to type in it. As soon as i remove/change this the input works again.
My React Component:
/** #jsx React.DOM */
var React = require('../../../../node_modules/react/react.js');
// react component DateBox will return a date time that updates every 50 miliseconds and appends it to a div with an id of 'date_box'
var GoogleSearchBox = React.createClass({
GetValueFromGoogleSearchInput: function(event){
var GoogleSearch = document.getElementById("google_search_input");
var UserSearchQuery = GoogleSearch.value.toString().replace(/ /g,"+");
var GET = "http://google.com/#q=" + UserSearchQuery;
window.open(GET, '_blank');
},
//rendering the html elements with the state.clock
render: function() {
return (
<div className="google_search">
<input type="text" value="" placeholder="Type here to search google" name="google_search_input" id="google_search_input">
</input>
<div>
<button id="google_search_button" onClick={this.GetValueFromGoogleSearchInput}></button>
</div>
</div>
);
}
});
//rendering with react to put onto the html
module.exports = GoogleSearchBox;
How it renders in html:
<div id="google_search_box">
<div class="google_search" data-reactid=".0">
<input type="text" value="" placeholder="Type here to search google" name="google_search_input" id="google_search_input" data-reactid=".0.0">
<div data-reactid=".0.1">
<button id="google_search_button" data-reactid=".0.1.0"></button>
</div>
</div>
</div>
Your text field is not responding to user input because you created a Controlled Component by manually setting the value prop.
An <input> with value set is a controlled component. In a controlled
<input>, the value of the rendered element will always reflect the
value prop.
If you would like to set a default value but allow the text field to respond to user input, consider setting the defaultValue prop instead.
<input type="text" defaultValue="" placeholder="Type here to search google" name="google_search_input" id="google_search_input">
My form has different fields for each type:
<form>
<select ng-model="message.type">
<option>SMS</option>
<option>Email</option>
</select>
<div ng-if="message.type=='sms'">
<textarea ng-model="message.body"></textarea>
</div>
<div ng-if="message.type=='email'">
<input type="text" ng-model="message.subject" />
<textarea ng-model="message.body" class="ckeditor"></textarea>
</div>
</form>
It works great BUT when the user selects email first, writes something in the subject field, then changes back to sms type, then when I send the message object in $http.post, it sends the object with subject child, even though the dom element was deleted.
How do I fix it? I want to send only the data that is linked to existing dom elements.
Thanks
I can imagine several solutions:
Delete the inappropriate attribute
You can do this when the selected option change:
$scope.deleteInappropriateAttribute = function () {
if ($scope.message.type !== 'email') {
delete $scope.message.subject;
}
};
<select ng-model="message.type" ng-change="deleteInappropriateAttribute()">
<option>SMS</option>
<option>Email</option>
</select>
It's even better to make this action just before you post the form, in order to not force the user to rewrite the message.subject if he's switching frequently between the different options.
Associate a different variable to each part of the form
<form>
<select ng-model="message.type">
<option>SMS</option>
<option>Email</option>
</select>
<div ng-if="message.type=='sms'">
<textarea ng-model="message.global.body"></textarea>
</div>
<div ng-if="message.type=='email'">
<input type="text" ng-model="message.email.subject" />
<textarea ng-model="message.global.body" class="ckeditor"></textarea>
</div>
</form>
And then, only post the appropriate variable:
$http.post(
'myUrl.php',
angular.extend(
{},
$scope.message.global,
$scope.message[$scope.message.type]
)
);
Bit of a weird question. I have got a form and inside this form the controls are div's with onClick events instead of buttons. I can't use buttons as I can't use page reloads, instead I have to send all data using ajax.
Plus half of the buttons just increase counters, below is my code. How would I go about using JavaScript to find the form ID that the element clicked on is in. So as an example:
<form id="20">
...
<div onClick="doSomething(this)">
...
</form>
The doSomething will then keep moving up levels of parents or something like that until it finds the form, and then a variable will have the form id assigned to it.
<form id="50">
<div class="image_container background">
<div style="text-align:center; font-size:12px;">image1</div>
<input type="hidden" id="imgId" value="50" />
<div class="image" style="background-image:url(images/image3.JPG);"></div>
<div class="selected_product">
<span>KR</span>
</div>
<input type="hidden" id="applied_products" value="KR" />
</div>
<div class="controls_container background">
<div id="selected">KR</div>
<a class="button arrow expandProducts">
<span>
<div class="products">
<span>KR</span>
<span>A5</span>
<span>CC</span>
</div>
</span>
</a>
<hr />
<span class="button plus" onClick="sale(this)"></span>
<input type="text" id="amount" disabled="disabled" value="0"/>
<span class="button minus"></span>
<hr />
<input type="text" id="total" disabled="disabled" value="£0"/>
</div>
</form>
Your doSomething function could continue navigating through it's parents until it finds a form, like so:
function doSomething( elem )
{
var parent = elem.parentNode;
if( parent && parent.tagName != 'FORM' )
{
parent = doSomething(parent);
}
return parent.id;
}
Also, if you use <button type="button">...</button> it won't cause a page refresh, since the default button type is submit.
If you are going straight javascript and not jquery or some other library. Each DOM Element has a parentNode Property
var elm = document.getElementById(yourDivId);
var parent = elm.parentNode;
From there you can cycle through each parent, until you get back to the form element and then pull out the id.
How about something like that:
function handler(target) {
var parent = target.parentNode;
// loop until parent is a form or browser crashes :)
while (parent.tagName != 'FORM') {
parent = parent.parentNode;
}
var formId = parent.id; // the id you wanted
// do stuff
}