Whats the proper way to have a form submit data in a stateless react component? - javascript

I have a stateless react component that is a little pop up. It takes some data from the user, and passes that back to its parent, where it executes the work.
What is the best way for this component to have a handleSubmit() function, that takes the user input, and sends it back to the parent?
import React, { Component } from "react";
import "../../../node_modules/bulma/css/bulma.css";
const Transfer = (props, token, web3) => {
return (
<div className="modal is-active">
<div className="modal-background" onClick={props.onClick} />
<div className="modal-card">
<section className="modal-card-body">
<div className="content">
<h1 className="title"> Transfer Tokens </h1>
<p className="has-text-danger">
Requires that you are the owner of the token you are transferring
</p>
<p className="subtitle">How it works</p>
<p className="">
Enter the ID of the token you want to transfer, the address to
whom its going to, and thats it!
</p>
//problem area
<form onSubmit={props.onClickSubmit}>
<label htmlFor="text">Address to recieve token</label>
<input
name="Address"
className="input is-info "
required="true"
/>
<label htmlFor="number">Token ID</label>
<input
className="input is-info"
name="Token ID"
type="number"
required="true"
/>
<a className="button is-pulled-right">Submit</a>
</form>
</div>
</section>
<footer className="modal-card-foot is-clearfix">
<a className="button" onClick={props.onClick}>
Cancel
</a>
</footer>
</div>
</div>
);
};
export default Transfer;
I pass in as a prop, onClickSubmit, in my parent component, and that contains the logic for what I'm trying to do.
Very new to stateless react components

It will be difficult to accomplish what you want with a stateless component since you cannot use either refs or state in a stateless component. You can think of a stateless component as a pure function that returns a piece of UI depending on the props you give it.
You could instead use a stateful component and e.g. store the input values in state and call the onClickSubmit prop function with this state when the user submits the form.

If you want to build stateless forms component, I send you a lib that I'm working on:
react-distributed-forms
This allow you to build your Transfer Component this way, (pay attention to use Input instead of input and Button instead of button):
import React, { Component } from "react";
import "../../../node_modules/bulma/css/bulma.css";
import { Input, Button } from "react-distributed-forms";
const Transfer = (props, token, web3) => {
return (
<div className="modal is-active">
<div className="myForm">
<label htmlFor="text">Address to receive token</label>
<Input name="Address" className="input is-info " required="true" />
<label htmlFor="number">Token ID</label>
<Input
className="input is-info"
name="Token ID"
type="number"
required="true"
/>
<Button name="submit" className="button is-pulled-right">
Cancel
</Button>
</div>
</div>
);
};
export default Transfer;
And then in your parent Component, wherever it is in the hierarchy, you simply do:
<Form onSubmit={({ name }) => { console.log(name); }} onFieldChange={({ name, value} ) => { console.log(name, value); }}>
...whatever
<Transfer />
...whatever
</Form>
onFieldChange will receive every input change.
onSubmit will receive the attribute "name" on the Button when you click it.
react-distributed-forms use React context API, so you don't have to pass directly props, it just works. Is built for really dynamic forms...

Related

React.js - How do I uniquely identify a field within a dynamically generated component?

I am currently creating a form that has an unknown number of sensor fields within it. I've gotten the front end working beautifully. However, now I want to grab the user info out of those dynamically generated component fields and I can't figure out how. Here is where I'm generating the components:
{this.state.sensors.map((item, i) => (
<UpdateSensorInfo
key={i}
sensorName={item.sensorName}
sensorLowerLimit={item.sensorLowerLimit}
sensorUpperLimit={item.sensorUpperLimit}
/>
))}
And here is the actual component itself:
import React, { Component } from "react";
import "./updateSensorInfo.css";
class UpdateSensorInfo extends Component {
render() {
return (
<div>
<div className="sensorInfoFrame">
<div className="sensorFieldBody">
<label className="sensorTextFieldLabel">Sensor Name:</label>
<input
type="text"
name="text"
placeholder=""
className="sensorTextField"
defaultValue={this.props.sensorName}
required
/>
</div>
<div className="sensorFieldBody">
<label className="sensorTextFieldLabel">Sensor Upper Limit:</label>
<input
type="number"
name="text"
placeholder=""
className="sensorTextField"
defaultValue={this.props.sensorUpperLimit}
required
/>
</div>
<div className="sensorFieldBody">
<label className="sensorTextFieldLabel">Sensor Lower Limit:</label>
<input
type="number"
name="text"
placeholder=""
className="sensorTextField"
defaultValue={this.props.sensorLowerLimit}
required
/>
</div>
</div>
</div>
);
}
}
export default UpdateSensorInfo;
I would like to uniquely identify each text field within each generated component. How can I do this?
For anyone who has a similar situation, I have found a solution that works. There is a way to use the key that is created for each of the components within each component. use the following as an id or name for the input element:
id={`${this._reactInternalFiber.key}-additionalNameHere`}
This uses the key and will allow you to loop over the inputs within the components.

ReactJS passing state to another class

i have problem in accesing the state on the other component. I am beginner in using the reactjs. I am still confuse on how to use the state and props. I am getting the input which will be set to the state as username then I will pass it to the formDetails component and use it when the handleSubmit triggers. can someone help me I would appreciate any comments, answers, suggestions.
//Components
class DaysForm extends React.Component {
constructor() {
super();
this.state = {
username: ""
};
this.updateInput = this.updateInput.bind(this);
}
onCalculate(e) {
$("#myModal").modal();
}
updateInput(event) {
this.setState({ username: event.target.value });
}
render() {
return (
<div>
<p>How many days are in a full time working week?</p>
<input
type="text"
className="form-control"
onChange={this.updateInput}
/>
<p>How many days a week will this employee work?</p>
<input type="text" className="form-control" />
<br />
<center>
<button
className="btn btn-default"
onClick={this.onCalculate.bind(this)}
>
Calculate
</button>
</center>
</div>
);
}
}
class FormDetails extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
//console.log here the state that is comming from the component
}
render() {
return (
<div>
<p>Email Address:</p>
<input type="text" className="form-control" />
<p>Business Name:</p>
<input type="text" className="form-control" />
<p>Phone Number:</p>
<input type="text" className="form-control" />
<br />
<center>
<button
className="btn btn-default"
onClick={this.handleSubmit}
>
Submit
</button>
</center>
</div>
);
}
}
From my understanding, you want to set the username in the DaysForm component, and then upon handleSubmit being clicked, it will show up in the FormDetails component. Is that correct?
Assuming so, there are a couple things missing.
First of all, you are not currently rendering FormDetails in the DaysForm. So right now, they have no relation to each other. Or at least not in the code you displayed. Are you using the username as a redux state perhaps which is share in the two components? Because this changes everything if that's the case.
The easier way to do this would be to have FormDetails be a child of DaysForm and then pass the username state as a prop in the FormDetails component. This will make it look something like this:
class DaysForm extends React.Component {
// your code
render(){
return(
<div>
<p>
How many days are in a full time working week?
</p>
<input type="text" className="form-control" onChange={this.updateInput}/>
<p>
How many days a week will this employee work?
</p>
<input type="text" className="form-control"/>
<br></br>
<center>
<button className="btn btn-default" onClick={this.onCalculate.bind(this)}>Calculate</button>
</center>
<FormDetails username={this.state.username}/>
</div>
)
}
}
However, you mentioned that the components have no relation to each other, I am assuming that there are no other components that can connect the two, so this complicates a bit when you want to pass in a state from one component to another. Based on the post's tags, I am assuming you are using Redux. If not, for this case I would encourage you to do so, since it will handle your state in between components. Of course if you haven't touched any of that yet, I will admit it is a whole new can of worms to open.
As for the difference between state and props. State usually belong to a component, you change them causing the component to rerender and they may be later passed in as a props for a child component (as shown in the example code). Props on the other hand come from other components and are immutable. See more here. The way you are using the state in DaysForm seems to be just fine and is as expected, the main challenge here is figuring out how to pass that state to FormDetails when there are no obvious other components in between them.

ReactJS props are updated, but field is not re-rendered

I have been playing around with fields in React, but am running into the issue where a text input is not updating (whilst the properties are). I have tried to do it through a class/state component, but to no avail. I was kind-of hoping to be able to make specific form-only components that are completely cut-off from stores. Can anyone point me in the right direction?
import React from 'react';
export default (props) => {
function editProjectNameChanged(event, syntheticEvent) {
debugger;
props.editProject.name = syntheticEvent.target.value;
}
return (
<div className="col-xs-5 col-md-5">
<form>
<div className="form-group">
<label htmlFor="management-projects-name">Project name</label>
<input type="text" className="form-control" id="management-projects-name"
value={props.editProject.name}
onUserInput={editProjectNameChanged.bind(null, event)}
placeholder="Project name" />
</div>
</form>
</div>
);
}
Thanks in advance.. This whole form thing is more difficult than I thought it would!
You shouldn't change the props in the child. That is a good job for the state. It's not a good idea to update props because parent component is not notified of the change and the state is more useful because when you call setState() the component is redraw. Something like that (I haven't tested it)
import React from 'react';
export class YourComponent extends React.Component{
constructor(){
this.state = {
name: this.props.editProject.name
};
}
editProjectNameChanged(event, syntheticEvent) {
this.setState({
name: syntheticEvent.target.value
});
}
render(){
return <div className="col-xs-5 col-md-5">
<form>
<div className="form-group">
<label htmlFor="management-projects-name">Project name</label>
<input type="text" className="form-control" id="management-projects-name"
value={props.editProject.name}
onUserInput={editProjectNameChanged.bind(this, event)}
placeholder="Project name" />
</div>
</form>
</div>;
}
}
You shouldn't mutate props the way you do it from within a component. If you do not want your component do manage the state, it should instead accept a callback as a prop, that it would call on user input.
The parent component would then decide whether or not, and how, to update the value, and pass it back to your component as a prop.
<input value={ props.value } onChange={ e => props.onChange(e.target.value) } />
Mutating directly the props object has no effect since version 0.13. You can find here the blog post explaining why this behaviour was introduced

Multiple Registration Form with redux and react

I have been trying to develop a dashboard form similiar to airbnb listing form for understanding more deeply about react redux but i am stuck in the middle of my project. I have a multiple form where when user clicks on continue button the user will get another form to fill and so on and if user clicks on back button the user will get form of one step back with previously filled values. I could not decide what should i do for this. Do i have to create a object in action as listingName . summary, name, email etc as empty value and update it with reducer using Object.assign() or what. Till now i could only develop like when user clicks on personal tab a form related to personal information is shown and when user clicks on basic tab, a form related to basic information is shown. I want all form data to be send to server at last submit. What should i do now ? Do i use increment and decrement action for the continue and back button and use submit action on the last form button ? Could you please provide me an idea ?
Here's my code
actions/index.js
export function selectForm(form){
return{
type: 'FORM_SELECTED',
payload: form
};
}
reducers/reducer_active_form.js
export default function(state=null, action){
let newState = Object.assign({},state);
switch(action.type){
case 'FORM_SELECTED':
return action.payload;
}
return state;
}
reducers/reducer_form_option.js
export default function(){
return[
{ option: 'Personal Information', id:1},
{ option: 'Basic Information', id:2 },
{ option: 'Description', id:3},
{ option: 'Location', id:4},
{ option: 'Amenities', id:5},
{ option: 'Gallery', id:6}
]
}
containers/form-details
class FormDetail extends Component{
renderPersonalInfo(){
return(
<div className="personalInfo">
<div className="col-md-4">
<label htmlFor='name'>Owner Name</label>
<input ref="name" type="textbox" className="form-control" id="name" placeholder="Owner name" />
</div>
<div className="col-md-4">
<label htmlFor="email">Email</label>
<input ref="email" type="email" className="form-control" id="email" placeholder="email" />
</div>
<div className="col-md-4">
<label htmlFor="phoneNumber">Phone Number</label>
<input ref="phone" type="textbox" className="form-control" id="phoneNumber" placeholder="phone number" />
</div>
<div className="buttons">
<button className="btn btn-primary">Continue</button>
</div>
</div>
);
}
renderBasicInfo(){
return(
<div>
<h3>Help Rent seekers find the right fit</h3>
<p className="subtitle">People searching on Rental Space can filter by listing basics to find a space that matches their needs.</p>
<hr/>
<div className="col-md-4 basicForm">
<label htmlFor="price">Property Type</label>
<select className="form-control" name="Property Type" ref="property">
<option value="appartment">Appartment</option>
<option value="house">House</option>
</select>
</div>
<div className="col-md-4 basicForm">
<label htmlFor="price">Price</label>
<input type="textbox" ref="price" className="form-control" id="price" placeholder="Enter Price" required />
</div>
<div className="buttons">
<button className="btn btn-primary">Back</button>
<button className="btn btn-primary">Continue</button>
</div>
</div>
);
}
renderDescription(){
return(
<div>
<h3>Tell Rent Seekers about your space</h3>
<hr/>
<div className="col-md-6">
<label htmlFor="listingName">Listing Name</label>
<input ref="name" type="textbox" className="form-control" id="listingName" placeholder="Be clear" />
</div>
<div className="col-sm-6">
<label htmlFor="summary">Summary</label>
<textarea ref="summary" className="form-control" id="summary" rows="3"></textarea>
</div>
<div className="buttons">
<button className="btn btn-primary">Back</button>
<button className="btn btn-primary">Continue</button>
</div>
</div>
);
}
renderLocation(){
return(
<div>
<h3>Help guests find your place</h3>
<p className="subtitle">will use this information to find a place that’s in the right spot.</p>
<hr/>
<div className="col-md-6">
<label htmlFor="city">City</label>
<input ref="city" type="textbox" className="form-control" id="city" placeholder="Biratnagar" />
</div>
<div className="col-md-6">
<label htmlFor="placeName">Name of Place</label>
<input ref="place" type="textbox" className="form-control" id="placeName" placeholder="Ganesh Chowk" />
</div>
<div className="buttons">
<button className="btn btn-primary">Back</button>
<button className="btn btn-primary">Continue</button>
</div>
</div>
);
}
render(){
if ( !this.props.form){
return this.renderPersonalInfo();
}
const type = this.props.form.option;
console.log('type is', type);
if ( type === 'Personal Information'){
return this.renderPersonalInfo();
}
if ( type === 'Basic Information'){
return this.renderBasicInfo();
}
if ( type === 'Description'){
return this.renderDescription();
}
if ( type === 'Location'){
return this.renderLocation();
}
}
}
function mapStateToProps(state){
return{
form: state.activeForm
};
}
export default connect(mapStateToProps)(FormDetail);
The first thing you're missing is controlled components. By giving the inputs a value property, and an onChange function, you will link the input with an external state.
Your components should have access, via react-redux, to the state and actions needed. The value of the form should be your state for that object. So you might have a state like:
location: {
listingName: '123 Main St',
summary: 'The most beautiful place!'
}
Then, you'd just pass each property to inputs. I'm assuming, in this example, that you've passed the location prop in mapStateToProps, and an actions object with all the related actions in mapDispatchToProps:
changeHandler(ev, fieldName) {
const val = ev.target.value;
this.props.actions.updateField(fieldName, val);
},
render() {
return (
<input
value={this.props.location.listingName}
onChange={(ev) => { this.changeHandler(ev, 'listingName'}}
/>
);
}
You provide it an action that can be used to update the state:
function updatefield(field, val) {
return {
type: UPDATE_FIELD,
field,
val
};
}
Then, you just merge it in, in your reducer
switch (action.type) {
case UPDATE_FIELD:
state = { ...state, [action.field]: val };
(using dynamic keys and spread operator for neatness, but it's similar to Object.assign)
All of your form state lives in the Redux store this way. When you are ready to submit that data to the server, you can either use async actions with redux-thunk, or set up some middleware to run the calls. Either way, the strategy is the same; your state lasts locally and populates all your forms, and then is sent to the server when the user submits.
I went through this pretty quick, let me know if you need me to elaborate on anything :)
As you are using react-redux you can use the redux-form. It will greatly help you with the coding as it will simplify your work load and it is also bug-free (as far as I know). In my opinion you would want to use all the libraries/frameworks provided to you as you want to be as agile as possible.
Also the redux-form has a wizard form implementation. I think that is exactly what you are looking for.
http://erikras.github.io/redux-form/#/examples/wizard?_k=yspolv
Just follow the link and you will see a very good tutorial on how to implement it. Should be a piece of cake.

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