How to submit a dynamic form in React.js - javascript

I have a dynamically created from in React, and I'd like to be able to submit the values of all the input fields, but I can't add seperate on change handlers for each input elment, as they are created dynamically:
extract from the form js:
const FormElements = ({formFields}) => ( <div> {
formFields.map(formField => ( <FormElement name={formField.name} type={formField.fieldType} />)
)} </div> );
console.log(formFields);
return (
<div class="col-md-12">
<div class="panel panel-primary">
<div class="panel-heading">
<h4 class="panel-title">
{title} - {id}
</h4>
</div>
<div class="panel-body">
<form >
<FormElements formFields={formFields} />
<a
class="btn btn-primary"
onClick={this.handleSubmitButton}//what do I do with this function?
href="#">Submit</a>
</form>
</div>
</div>
</div>
);
form element js:
export default class FormElement extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div class="form-group">
<label for="{this.props.name}">{this.props.name}</label>
<input type="{this.props.type}}" class="form-control" id="{this.props.name}" placeholder="blah blah" />
</div>
);
}
}

Since they are controlled inputs there is not a react way to that, and even if there is I would not recommend it, React is all about declarative code.
There are two ways to solve this, one is to use make a property onChange on your FormElement and pass a function with ids, something like this:
<FormElements onChange={(key, value) => this.setState({ [key]: value })
The other way is to send give all the not defined props to the input:
export default class FormElement extends React.Component {
constructor(props) {
super(props);
}
render() {
const { name, type, ...other } = this.props
return (
<div class="form-group">
<label for="{name}">{name}</label>
<input type="{type}}" class="form-control" {...other} id="{this.props.name}" placeholder="blah blah" />
</div>
);
}
}
(the { [key]: value } and {...other} is ES6)

I actually managed this in a quite convoluted, and probably not recommended way, but it works! I've also never seen this done elsewhere...probably for good reason:
Form element:
export default class FormElement extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.props.handleChange(e.target.id, e.target.value);
}
render() {
return (
<div class="form-group">
<label for={this.props.id}>{this.props.name}</label>
<input type="{this.props.type}}" class="form-control" id={this.props.id} value={this.props.value} placeholder="blah blah" onChange={this.onChange}/>
</div>
);
}
}
form:
handleFormElementChange(id, value) {
console.log("changing: " + id + " = "+ value);
var frm = this.state.formData;
var index=-1;
for(var i=0;i<frm.length;i++) {
if(frm[i].id==id) {
index=i;
break;
}
}
frm[index].value = value;
this.setState({formData: frm});
}
const FormElements = ({formFields}) => ( <div> {
formFields.map(formField => ( <FormElement name={formField.name} key={formField.id} value={formField.value} id={formField.id} type={formField.fieldType} handleChange={this.handleFormElementChange.bind(this)}/>)
)} </div> );
What's happening is the actual full form data is being updated in the form component, and each time a change is made to one of the form elements, it passes it back to the parent form, update's the form's sate and then re-renders the whole form.
The complication here was actually finding the correct form element in the overall form status, by searching through the array for the key, and updating the value.
While I see this working with small forms, I can see how it would start to significantly slow down rendering on large form applications.

Related

Get prop value from div in React

A working example of my problem can be found at:
https://codepen.io/RyanCRickert/pen/vYYQeaW
I am prop drilling a function two levels and passing that function along with an index to a rendered component. When a name is submitted it renders a new component which shows the name and div which has an onClick (X). I am trying to receive the index of where the name is located in the array which it lives so that I may splice it out when the button is clicked.
If I enter the name "Bob" for example, then click the div with the listener I can console log the event.target. Using the above example I get "<div class='person-item__X' value='0'>X</div>" for event.target and undefined for event.target.value. The value is being assigned as <div onClick={props.removeName} class="person-item__X" value={props.value}>X</div>.
Am I just unable to grab the value of a div in such a manor? Or is there something that I am missing? Thank you
Change these to your code
const PersonListItem = props => (
<div class="person-item">
<div class="person-item__name">{props.name}</div>
<div onClick={() => props.removeName(props.value)} class="person-item__X" value={props.value}>X</div>
</div>
);
Inside PeopleList replace this line
<PersonListItem key={index} name={person} value={index} removeName={(id) => props.removeName(id)} />
Inside TeamGenerator replace this line
<PeopleList people={this.state.names} removeName={(id) => this.handleRemoveName(id)} />
now in handleRemoveName you will recieve a id of the item on which X was clicked
handleRemoveName = id => {
const currentArr = this.state.names;
console.log(id);
}
In your case, to grab the value inside this div, you should use ref API.
Your code should look like this:
TeamGenerator.js
import React, { Component } from "react";
import CustomModal from "./Modal";
import PeopleList from "./PeopleList";
import "./index.css";
export default class App extends Component {
constructor(props) {
super(props);
// Create a ref
this.divTextRef = React.createRef();
this.state = {
names: [],
selectedName: ""
};
}
handleCloseModal = () => {
this.setState({
selectedName: ""
});
};
handleChange = e => {
this.setState({ name: e.target.value });
};
handleRemoveName = index => {
// Get your name and index this way
console.log("Your text: ", this.divTextRef.current.innerHTML);
console.log("Your index: ", index);
};
handleSubmit = e => {
e.preventDefault();
const currentNames = this.state.names;
if (this.state.name)
currentNames.push(
this.state.name[0].toUpperCase() + this.state.name.slice(1)
);
this.setState({
name: "",
names: currentNames
});
};
render() {
return (
<div className="container">
<CustomModal
selectedName={this.state.selectedName}
closeModal={this.handleCloseModal}
/>
<form onSubmit={this.handleSubmit}>
<label>
Add name:
<input
type="text"
value={this.state.name}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
<div className="people-list-container">
<PeopleList
people={this.state.names}
removeName={this.handleRemoveName}
upperRef={this.divTextRef} // Pass the ref down from your Component tree
/>
</div>
</div>
);
}
}
PeopleList.js
import React from "react";
import PersonListItem from "./PersonListItem";
export default class PeopleList extends React.Component {
render() {
return (
<div className="people-container">
<div className="people-title">List of people</div>
<div className="people-list">
{this.props.people.length === 0 ? (
<div className="people-item">
<span>No people added</span>
</div>
) : (
this.props.people.map((person, index) => (
<PersonListItem
key={index}
name={person}
value={index}
removeName={() => this.props.removeName(index)} // Passing index to the removeName function of Parent
upperRef={this.props.upperRef} // Continue passing it down to PersonListItem
/>
))
)}
</div>
</div>
);
}
}
PersonListItem.js
import React from "react";
const PersonListItem = props => (
<div className="person-item">
<div ref={props.upperRef} className="person-item__name"> // Use the passed ref
{props.name}
</div>
<div
onClick={props.removeName}
className="person-item__X"
value={props.value}
>
X
</div>
</div>
);
export default PersonListItem;
The div node does not have the value like input, so you can not grab it by your old way.

Passing ref up to parent in react

I have a little app that has an input and based on the search value, displays weather for a particular city. I'm stuck at a certain point though. The idea is that once you search a city, it hides the text input and search button and displays some weather info and another search button to search a new city. My issue is that I want to focus on the search box once I click to search again. I hope that makes sense. I read that the ideal way to do this is with refs. I wired it up like such:
class WeatherForm extends React.Component {
constructor(props) {
super(props);
this.city = React.createRef();
}
componentDidMount() {
this.props.passRefUpward(this.city);
this.city.current.focus();
}
render() {
if (this.props.isOpen) {
return (
<div className={style.weatherForm}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
)
} else {
return (
<div className={style.resetButton}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
);
}
}
}
With this I can pass that ref up to the parent to use in my search by using this.state.myRefs.current.value; It works great, but when I try to reference this.state.myRefs.current in a different function to use .focus(), it returns null.
resetSearch = () => {
console.log(this.state.myRefs.current); // <- returns null
this.setState({
isOpen: !this.state.isOpen,
details: [],
video: []
});
}
Is this because I'm hiding and showing different components based on the search click? I've read numerous posts on SO, but I still can't crack this. Any help is appreciated. I'll include the full code below. To see it in full here is the git repo: https://github.com/DanDeller/tinyWeather/blob/master/src/components/WeatherMain.js
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {
recentCities: [],
details: [],
isOpen: true,
myRefs: '',
video: '',
city: ''
};
this.updateInputValue = this.updateInputValue.bind(this);
this.getRefsFromChild = this.getRefsFromChild.bind(this);
this.resetSearch = this.resetSearch.bind(this);
this.getWeather = this.getWeather.bind(this);
}
updateInputValue = (e) => {
...
}
resetSearch = () => {
console.log(this.state.myRefs.current);
this.setState({
isOpen: !this.state.isOpen,
details: [],
video: []
});
}
getWeather = (e) => {
...
}
getRefsFromChild = (childRefs) => {
...
}
render() {
return (
<section className={style.container}>
<div className={style.weatherMain + ' ' + style.bodyText}>
<video key={this.state.video} className={style.video} loop autoPlay muted>
<source src={this.state.video} type="video/mp4">
</source>
Your browser does not support the video tag.
</video>
<div className={style.hold}>
<div className={style.weatherLeft}>
<WeatherForm
updateInputValue={this.updateInputValue}
getWeather={this.getWeather}
passRefUpward={this.getRefsFromChild}
resetSearch={this.resetSearch}
isOpen={this.state.isOpen}
/>
<WeatherList
details={this.state.details}
city={this.state.city}
isOpen={this.state.isOpen}
/>
</div>
<div className={style.weatherRight}>
<Sidebar
recentCities={this.state.recentCities}
/>
</div>
<div className={style.clear}></div>
</div>
</div>
</section>
);
}
}
class WeatherForm extends React.Component {
constructor(props) {
super(props);
this.city = React.createRef();
}
componentDidMount() {
this.props.passRefUpward(this.city);
this.city.current.focus();
}
render() {
if (this.props.isOpen) {
return (
<div className={style.weatherForm}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
)
} else {
return (
<div className={style.resetButton}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
);
}
}
}
export default Weather;
You try to achieve unmounted component from DOM, because of this you can not catch the reference. If you put this code your instead of render function of WeatherForm component, you can catch the reference. Because i just hide it, not remove from DOM.
render() {
return (
<div>
<div className={style.weatherForm}
style={this.props.isOpen ? {visibility:"initial"} :{visibility:"hidden"}}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
<div className={style.resetButton} style={this.props.isOpen ? {visibility:"hidden"} :{visibility:"initial"}}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
</div>
)
}
console.log(this.state.myRefs.current) returns null , because it's a reference to an input dom element which does not exists as currently Weather form is displaying Search another city along with a reset button.
In reset function state changes, which results in change of prop isOpen for WeatherForm component. Now, screen would be displaying the input field along with search button.
After component is updated ComponentDidUpdate lifecycle method is called.
Please add ComponentDidUpdate lifecycle method in WeatherForm and add ,
this.city.current.focus() in the body of method.
There is no need to pass reference of a dom element to the parent element as it is not consider as a good practise.
Edit 1 :-
Need to set input field in focus only if prop ( isOpen ) is true as we will get reference to the input field only if its mounted.
ComponentDidUpdate(){
if(this props.isOpen)
this.city.current.focus
}
Link to Lifecycle method :-
https://reactjs.org/docs/react-component.html#componentdidupdate
Hope this helps,
Cheers !!

Render HTML from a json string in react

I'm trying to render an HTML object from a JSON string that I'm receiving from an API. I'm able to get the string to render to HTML successfully but it shows the entire JSON string. I'm only looking to get specific values (Phone, Name, Id.) What would be the best way for me to extract specific values from my JSON array and format it in HTML? I'm referring to records by state but I'm unable to get any sub-value of record in the render process.
class menuScreen extends React.Component {
constructor(props) {
super(props)
const data = store.getState();
this.state = {
username: '',
messages: data.messages
}
}
handleSearch(e) {
this.setState({username: e.target.value})
}
handleChange(evt) {
this.setState({
username: evt.target.value.substr(0, 100)
});
}
onLinkClicked() {
var conn = new jsforce.Connection({serverUrl: 'https://cs63.salesforce.com', accessToken: sessionStorage.getItem('token')})
var parent = this.state.username
//console.log(this.state.username)
conn.sobject("Contact").find({
LastName: {
$like: parent
}
}, 'Id, Name, Phone'
).sort('-CreatedDate Name').
limit(5).skip(10).execute(function(err, records) {
if (err) {
return console.error(err);
}
for (var i = 0; i < records.length; i++) {
var record = (records[i]);
console.log("Name: " + record.Name); //these are the records I'm trying to render
console.log("Phone: " + record.Phone);
} this.setState({records : records})
}.bind(this));
}
render() {
return (
<div className='menubox' id='menubox'>
<div className='searchbar-container'>
<form onSubmit={e => e.preventDefault()}>
<input type='text' size='25' placeholder='Contact Last Name' onChange={this.handleChange.bind(this)} value={this.state.username}/>
<button type='submit' onClick={this.onLinkClicked.bind(this)}>
Search
</button>
</form>
</div>
<div>
<div dangerouslySetInnerHTML={ { __html: JSON.stringify(this.state.records) } }></div> //how can I show specific values, isntead of the entire string?
</div>
</div>
)
}
}
export default menuScreen;
JSON.parse your string into a JavaScript object. You can then do whatever processing you want on that object, such as removing fields you don't want, and then you can JSON.stringify it back into a JSON string which you can render.
Something like:
class BlahBlah extends React.Component {
constructor() {
//...some code...
this.processJson = this.processJson.bind(this)
}
//...a lot of code...
processJson(json) {
var object = JSON.parse(json)
var output = {}
//for every property you want
output[property] = object[property]
return JSON.stringify(output)
}
//...a lot more code...
render() {
return(
//...even more code...
<div dangerouslySetInnerHTML={ { __html: this.processJson(this.state.records) } }></div>
//...and yet more code.
)
}
}
You can run a map function and output the JSX for each item.
class menuScreen extends React.Component {
constructor(props) {
super(props)
const data = store.getState();
this.state = {
username: '',
messages: data.messages,
records: [],
};
}
render() {
return (
<div>
{this.state.records.map(record => (
<div>{record.attributes.name} {record.attributes.phone} {record.whatever}</div>
)}
</div>
);
}
}
Keep in mind, if you want a more complex HTML structure within map function, you'll have to wrap it in a single DOM node.
The full file would look like:
render() {
return (
<div className='menubox' id='menubox'>
<div className='searchbar-container'>
<form onSubmit={e => e.preventDefault()}>
<input type='text' size='25' placeholder='Contact Last Name' onChange={this.handleChange.bind(this)} value={this.state.username}/>
<button type='submit' onClick={this.onLinkClicked.bind(this)}>
Search
</button>
</form>
</div>
<div>
{this.state.records.map(record => (
<div>{record.attributes.name} {record.attributes.phone}</div>
)}
</div>
</div>
);
}
You could create a separate render method that will render your records like so:
renderRecords(records) {
return records.map(r => <div> r.Name, r.Phone</div>);
}
And then call the method inside your render method, instead of using dangerouslySetInnerHTML, like so
render() {
return (
<div className='menubox' id='menubox'>
<div className='searchbar-container'>
<form onSubmit={e => e.preventDefault()}>
<input type='text' size='25' placeholder='Contact Last Name' onChange={this.handleChange.bind(this)} value={this.state.username}/>
<button type='submit' onClick={this.onLinkClicked.bind(this)}>
Search
</button>
</form>
</div>
<div>
<div>{ this.renderRecords() }</div>
</div>
</div>
)
}

Checkbox in react js doesn't change

I have main component as follows :
export default class RegistrationFormStepFour extends React.Component{
constructor(props){
super(props);
this.state = {
terms: false,
emailNotifications: false,
smsNotifications: false,
errors: {}
}
}
handleTerms(event){
event.preventDefault();
this.setState({terms: !this.state.terms});
}
render(){
const language = this.props.currentLanguage;
return (
<div>
<Terms
nameYes="chcTerms"
text={language.termsText}
state={this.state.terms}
onChange={this.handleTerms.bind(this)}
currentLanguage={language}
error={this.state.errors.terms}/>
</div>
);
}
}
And component term is as follows :
import React from 'react';
const Terms = ({nameYes, text, state, onChange, error}) => {
let hasError = error ? "hasError" : "";
return (
<div className="col-lg-12 text-center" style={{marginBottom: 30}}>
<form>
<label className="radio-inline">
<input
type="checkbox"
name={nameYes}
checked={state}
onChange={onChange}
value=""/>
</label>
</form>
<p className={`questionsText ${hasError}`} style={{marginTop: 10}}>{text}</p>
</div>
);
};
export default Terms;
But when I click on the checkbox, nothing happens. If I console log the state in the terms component it show right value. First time is false, when I click on the checkbox than is true, but the checkbox isn't checked.
Any advice?
The event.preventDefault() is causing your problems in the controlled component checkbox.
http://www.matthiaslienau.de/blog/tags/checkbox
Checkboxes (and Radio Buttons): Manually updating state fails for checkboxes (and without having tested it: I think for radio controls as well). Why? The problem one will face is that the checkbox onChange event behaves in a special way since the era of HTML (how could I forget!): You may not toggle the state of a checkbox manually via the .checked property. Nor does React. The onChange (onClick) event is fired after the element state changed internally. This may just be reverted based on the return value of the event handler. See this post for a comprehensive examination of this fact.
const Terms = ({ nameYes, text, state, onChange, error }) => {
let hasError = error ? "hasError" : "";
return (
<div>
<form>
<label className="radio-inline">
<input
type="checkbox"
name={nameYes}
checked={state}
onChange={onChange}
/>
</label>
</form>
<p className={`questionsText ${hasError}`}>{text}</p>
</div>
);
};
class RegistrationFormStepFour extends React.Component {
constructor(props) {
super(props);
this.state = {
terms: false,
emailNotifications: false,
smsNotifications: false,
errors: {}
}
}
handleTerms(event) {
this.setState({ terms: event.target.checked });
}
render() {
const language = { termsText: 'Some Language' };
return (
<div>
<Terms
nameYes="chcTerms"
text={language.termsText}
state={this.state.terms}
onChange={this.handleTerms.bind(this)}
currentLanguage={language}
error={this.state.errors.terms}/>
{JSON.stringify(this.state)}
</div>
);
}
}
ReactDOM.render(<RegistrationFormStepFour />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
I think you should remove value attr from input tag. try this :
<input
type="checkbox"
name={nameYes}
checked={state}
onChange={onChange}
/>

Adding new inputs to component and tracking state in React

I have a React component that has a few simple input fields where I am currently tracking the state to eventually be placed into an AJAX call. I also have a button that, on click, will create a new row of input fields (same as the initial inputs).
I am pretty new to React and initially built out a simple function that clones the entire div and appends it to the .ticket-section div. I ran into some problems about inputs having the same react-id and it honestly felt like I was fighting the framework a little.
Any recommendations of how to create these new inputs and be able to track the state individually of the new row of inputs? Greatly appreciated in advance.
Here is my component:
var AddItem = React.createClass({
getInitialState: function() {
return {item_name: '', quantity: '', price: ''}
},
itemNameChange: function(e) {
this.setState({item_name: e.target.value});
},
quantityChange: function(e) {
this.setState({quantity: e.target.value});
},
priceChange: function(e) {
this.setState({price: e.target.value});
},
render: function() {
return (
<div>
<div className="ticket-section">
<div className="add-ticket">
<ul>
<li>
<label>Name</label>
<input id="item-name" type="text" placeholder="xyz item" value={this.state.item_name} onChange={this.itemNameChange} />
</li>
<li>
<label>Quantity Available</label>
<input id="quantity" type="number" placeholder="100" value={this.state.quantity} onChange={this.quantityChange} />
</li>
<li>
<label>Price</label>
<input id="price" type="number" placeholder="25.00" value={this.state.price} onChange={this.priceChange} />
</li>
</ul>
</div>
</div>
<button className="add-another-item">+ Add another item</button>
</div>
);
}
});
Thanks again.
I'm not sure but let me quess, are you looking for something like this?
class InputComponent extends React.Component {
constructor(props){
super(props)
}
render(){
return <div>
<input type="text"
onChange={this.props.change}/>
</div>
}
}
class Widget extends React.Component {
constructor(){
this.state = {
values: ['']
};
this.handleClick = this.handleClick.bind(this);
}
handleChange(index, e){
const oldState = this.state.values;
oldState[index] = e.target.value
this.setState({values: oldState})
}
handleClick(){
const oldState = this.state.values
oldState.push('');
this.setState({values: oldState})
}
render(){
const itemList = this.state.values.map((item, index)=>{
return <InputComponent key={index} change={this.handleChange.bind(this, index)}/>
});
console.log(this.state.values)
return <div>
{itemList}
<hr/>
<button onClick={this.handleClick}>Click</button>
</div>
}
}
React.render(<Widget />, document.getElementById('container'));
Fiddle example. I hope it will help you.
Thanks

Categories

Resources