I have a very basic comment form that takes some text input from a user and sends a POST request via AJAX to create a new comment.
var CommentForm = React.createClass({
propTypes: {
// ...
// ...
},
handleFormSubmit: function(e) {
e.preventDefault();
var component = this;
return $.ajax({
type: "POST",
url: this.props.someURL,
data: // ???? - Need to figure out how to serialize data here,
dataType: "json",
contentType: "application/json",
success: (function(response){ alert("SUCESS") }),
error: (function(){ alert("ERROR"); })
});
},
render: function() {
var component = this;
return (
<form onSubmit={component.handleFormSubmit} className="comments__form" id="new_comment" accept-charset="UTF-8">
// ...
</form>
);
}
});
I need to serialize the form data to send along with my POST request, but I'm not sure how. I know in JQuery I can select the form element and do something like $("#my_form").serialize(), but I can't seem to call that from inside the React component (not sure why?)
Some other stackoverflow answers suggested adding a ref='foo' to each relevant DOM element and then I can access them with React.findDOMNode(this.refs.foo).getValue();. This works fine but it leaves me to manually construct the whole serialized form data string, which isn't pretty if the form is a lot longer and complex.
// Sample serialized form string
"utf8=✓&authenticity_token=+Bm8bJT+UBX6Hyr+f0Cqec65CR7qj6UEhHrl1gj0lJfhc85nuu+j2YhJC8f4PM1UAJbhzC0TtQTuceXpn5lSOg==&comment[body]=new+comment!"
Is there a more idiomatic way to approach this - perhaps a helper that will let me serialize my form data within ReactJS?
Thanks!
You would usually use the component state to store all the information.
For example, when an inputs text is changed, you would change the state to reflect that:
<input type="text" onChange={ (e) => this.setState({ text: e.target.value }) }
Then, when it comes to submit the form you can access the value using this.state.text.
This does mean that you would have to build the object yourself however, although, this could be achieved via a loop:
this.state.map((value, index) => {
obj[index] = value;
});
and then send obj as the post data.
UPDATE
The only performance issue with updating the state is that the component is re-rendered each time. This isn't an issue and is the way react components work, as long as you watch for the size of the render method and what it is doing.
Forms in the usual HTML <form> tag sense, don't exist within React. You use the form components to change the state. I suggest reading up on React Forms
In terms of the UTF8 flag, that would be the value of a hidden field, so you could use refs in this case to get that value for your object:
<input type="text" ref="utf8" value="✓" />
obj.utf8 = this.refs['utf8'].value
For those who want to use Serialize form in ReactJs with functional component try this:
1. Create a state
const [form, setForm] = useState({});
2. Create a function handler
const handleForm = (name, value) => {
setForm({...form, [name]: value});
}
Now you got your object here:
const handleSubmit = () => {
console.log(form);
}
3. In your render
<form>
<input onChange={(e)=>handleForm('name', e.target.value)} />
<input onChange={(e)=>handleForm('phone', e.target.value)} />
<button onClick={()=>handleSubmit()}>Submit</button>
</form>
UPDATE 2022
Or use useForm
useForm is a custom hook for managing forms with ease. It takes one object as optional argument. The following example demonstrates all of its properties along with their default values.
Related
Im using MUIs input field for a lot of input sections of my questionnaire and want to save it to my state variable that is an object which holds all my form values. How can I manipulate my formData object in a reusable MUI component?
Im currently passing formData as a prop to the component but am unsure how to use the setFormData inside the component since it will be a different property each time I use it.
I was thinking each question is a property of the state object formData so itll look like
formData{question1: 'foo', question2: 'bar'}
This is how the form looks at the moment (i dont mind changing the structure if it makes sense to do so
Take a look at InputField on the form (i also attached the component code)
<QuestionLabel>Do you have a "Nickname":</QuestionLabel>
<InputField value={props.formData.nickname}/>
<QuestionLabel>Preferred Language:</QuestionLabel>
<InputField value={props.formData.language}/>
<QuestionLabel>Occupation:</QuestionLabel>
<InputField value={props.formData.occupation}/>
This is how the component looks (im aware i will have to change this)
export default function InputField(props){
return(
<TextField
fullWidth
value={props.value}
variant='standard'
/>
)
}
Disclaimer
First post so sorry if the format isn't perfect or if i attached the code snippets in a inconvenient way, please give me some pointers if thats the case
Since there is no two-way binding in react, the normal way of processing form input with controlled components is to pull the text from the specific dom element every time it changes. We do this using event.target.value to pull the data, but how do we then add it correctly to your state in a reusable component?
For that we would want to add a name tag to the input field, which we would then use when adding the value to our state. As you correctly stated, formData.someName = 'some text' We update our state in a changeHandler function that updates our state every time the input changes.
For example, assume we are passing setFormData, formData and a fieldName into the props:
export default function InputField(props){
const {setFormData, formData, fieldName} = props //destructure the props
const changeHandler = (event) => {
// updates the formData state of formData.name to equal event.target.value
setFormData({...formData, [event.target.name]: event.target.value})
}
return(
<TextField
fullWidth
name={fieldName}
value={formdata.fieldName}
onChange={changeHandler}
variant='standard'
/>
)
}
Using the above component would look something like this:
<InputField setFormData={setFormData} formData={formData} fieldName="question1">
I was able to get it working by passing the property into the component that needed to be changed; here is my handleChange function on the textfield component:
const handleChange = (event) => {
var formData = {...props.formData}
formData[props.property] = event.target.formData
props.onChange(formData)
}
I have all my states in my parent App.js. I have initialized states like so.
this.state = {
name: ''
}
I have a child component Child.js which is stateless.
Now I have a form in Child.js and when submitted, calls function onSubmit() which then sends data to my database.
There are two ways by which the data can be passed from the child component to the onSubmit function. I can pass the data as argument from Child.js and then use it as parameter in my App.js like this.
Child.js
<Button
title="Save"
onPress={() => this.props.onSubmit(myformvalue)}
/>
After this, I can just use the values as parameter in my onSubmit function like this.
onSubmit (value) {
fetch('myip', {
method: 'POST',
body: JSON.stringify({
customername: value,
}),
}
}
OR,
I can create a function in App.js that tracks the state change in Child.js's form and then use the current state when submitting like this.
App.js
changingstatefunc(NameParameter) {
this.setState ({
name: NameParameter,
})
}
onSubmit () {
fetch('myip', {
method: 'POST',
body: JSON.stringify({
customername: this.state.name,
}),
}
}
And In Child.js,
<TextInput
placeholder="Customer Name"
onChangeText={(customername) => this.props.changingstatefunc(customername)}
/>
I am not sure which one to choose as both work. In React.js they want you to use controlled input but in react-native, would it be okay to just use the first method to pass parameters instead of changing states if I do not need anything special to happen on each press of a button?
I think the best solution - create stateful Child component and control all inputs in this component, and onSubmit just pass data from Child state to App. It'll allow you to validate inputs in the Child component and the App component will be much readable
I'm using Reactjs. I have a form that will populate my database onSubmit with just a name property. Assuming inserting data is success, How do I jump to back to my landing page in the promise? my landing page url is a simple '/'. or should i jump back to the landing page somewhere else and not in the promise.
const React = require('react')
const axios = require('axios')
class AddNewRecordLabel extends React.Component {
constructor (props) {
super(props)
this.state = ({
artists: []
})
this.onSubmit = this.onSubmit.bind(this)
}
componentDidMount () {
axios.get('http://localhost:5050/recordLabels')
.then((res) => {
this.setState({
artists: res.data
})
})
}
onSubmit (e) {
e.preventDefault()
if (!this.refs.name.value) {
console.log('fill in the name input')
} else {
var object = {
name: this.refs.name.value
}
axios.post('http://localhost:5050/recordLabels', object)
.then((res) => {
//change here
})
}
}
render () {
return (
<div>
<h3>add new record label</h3>
<form onSubmit={this.onSubmit}>
<label>
<input type='text' ref='name' placeholder='name'/>
</label>
<br></br>
<button type='submit'> Add Record Label </button>
</form>
</div>
)
}
}
module.exports = AddNewRecordLabel
Typically, you would create a library using a flux pattern which uses dispatchers, actions and stores to control your components. If you want to save a lot of time, there are libraries using the flux pattern out there such as react-redux and react-flux.
I have used a home grown flux pattern before. I'm using redux now which is fairly easy to use as well as pretty quick to develop with from my personal experience. There's great documentation on it and a lot of support from the community.
If you want to keep it simple, you might want to rethink your strategy such as returning message that either replaces the form giving them options such as going back to the home page or even leaves the form so they have an opportunity to add another record label. You would also want to check to see if there was an error and show some sort of message stating why it was unsuccessful based on the response. If you want to go back to the home page you could simply add this to your promise...
window.location.href = '/'
...but ideally, you would want to move your service calls to another file and return responses, then act on those responses in your component accordingly, but the general recommended approach is to do it by dispatchers and listeners and update your state or props within this component.
I have dynamic JSON data and have a custom method to go through the JSON to dynamically render a form on the page. The reason for the JSON schema is to build various forms that is not predefined.
I have hooked up Redux so that the schema and the formValues below gets assigned as the props of this class. So, the form is rendering correctly with the correct label, correct input field types etc. When an onChange event happens on the fields, the app state(under formData) is being updated correctly. But I am noticing that when the formData changes in the app state, the entire form gets re-rendered, instead of just the "specific fields". Is this because I am storing the form values as an object under formData like this? How do I avoid this issue?
formData = {
userName: 'username',
firstName: 'firstName
}
Example schema
const form = {
"fields":[
{
"label":"Username",
"field_type":"text",
"name":"username"
},
{
"label":"First Name",
"field_type":"text",
"name":"firstName"
}
]
}
Redux state
const reducer = combineReducers({
formSchema: FormSchema,
formData: Form
});
//render method
render() {
const { fields } = this.props.form,
forms = fields.map(({label, name, field_type, required }) => {
const value = value; //assume this finds the correct value from the "formData" state.
return (
<div key={name}>
<label>{label}</label>
<input type={field_type}
onChange={this.onChange}
value={value}
name={name} />
</div>
);
})
}
//onchange method (for controlled form inputs, updates the fields in the formData app state)
onChange(event) {
this.props.dispatch(updateFormData({
field: event.target.name,
value: event.target.value
}));
}
From your example I'm not sure, but if you're rendering the whole thing in a single render() method, yes, the component will be rendered again. And that is the problem, THE component. If you are trying to have multiple components, then they should be split up as much as possible. Otherwise if the state changes, it triggers a re-render of the only component there is.
Try breaking it as much as you can.
Hints: (dont know if they apply but maybe)
use ref={}s
implement shouldComponentUpdate()
EDIT: Just thought about this, but are you storing the fields' values in your state? This doesnt feel correct. Be sure to read carefully the React guide about controlled components. (Eg try to render using plain <span>s instead of inputs, and listen to onKeyPress. Would it still work? If not you might be misusing the value attribute)
Here are the codes
import React from "react";
var newForm = React.createClass({
handleSubmit: function (e, text) {
e.preventDefault();
console.log(text);
},
render: function () {
return (
<form onSubmit={this.handleSubmit("react!!!")}>
<input type="text"/>
</form>
);
}
)};
module.exports = newForm;
What I want to achieve with this is that when I submit the form, I want the string "react!!!" to be printed out in the console and prevent the default event from taking place at the same time.
Obviously, passing in the argument to "handleSubmit" function breaks the code.
Is there anyway to pass in an argument from onSubmit event to the functions attached to this event?
You can curry the function
handleSubmit: text => event => {
event.preventDefault()
console.log(text)
}
<form onSubmit={this.handleSubmit('react!!!')}>
...
Equivalent ES5 code
handleSubmit: function(text) {
return function(event) {
event.preventDefault()
console.log(text)
}.bind(this)
}
Your code is weird though because you're mixing ES6 and ES5 styles. If you're using ES6 imports, you might as well use ES6 in the rest of your code
import {Component} from 'react'
class NewForm extends Component {
handleSubmit (text) {
return event => {
event.preventDefault()
console.log(text)
}
}
render() {
return (
<form onSubmit={this.handleSubmit('react!!!')}>
<input type="text">
</form>
}
}
}
export default NewForm
Or better, since your Component doesn't use any state, you can use a Stateless Functional Component
import {Component} from 'react'
const handleSubmit = text=> event=> {
event.preventDefault()
console.log(text)
}
const NewForm = props=> (
<form onSubmit={handleSubmit('react!!!')}>
<input type="text">
</form>
)
export default NewForm
Yes, but you shouldn't do it. What you are currently doing is saying 'set the onSubmit handler to being the result of calling this.handleSubmit('react!!!')'. What would work instead is to go:
<form onSubmit={e => this.handleSubmit(e, "react!!!")}>
which is of course just the short form of saying:
<form onSubmit={function(e) { this.handleSubmit(e, "react!!!"); }}>
However, although this will work, what this is doing is defining a new anonymous function every time render is called. Render can be called often, so this will end up with a bunch of new functions being created all the time, which can cause perf issues as they need to be garbage collected and cleaned up.
What is better is to have a function which is capable of finding the data you need on its own. There are 2 different ways which I use to address this:
Either store the relevant data in props, so that it can be pulled out from this.props
Or, store the relevant data in the DOM (using HTML 5 data attributes), and then pull them out using javascript. Since you are given the event target in the event handler that is called, you can use it to pull out any data attributes you have stored. For example you might declare your form like this:
<form data-somefield={something.interesting} onSubmit={this.handleSubmit}>
which you could access in handleSubmit by going:
handleSubmit: function(e) {
console.log(e.target.dataset.somefield);
}
The caveat there being that a) storing data in the DOM like this doesn't feel very idiomatic React, and b) if you use this for onClick events with nested elements, you might find that you have to climb up the DOM hierarchy to find where your data is stored. As an example if you have a div with a data attribute and an onClick but it contains a span and an image, when you click the image it's possible that is what the onClick handler will see as it's target element, meaning you'd have to go up to the parent to find the div with the data attribute you're looking for.
So it's probably best to just include the data you want in your component's props and pull it out from there in your event handler.