-
Notifications
You must be signed in to change notification settings - Fork 0
React Forms
- Controlled Form
- Controlled Vs Uncontrolled Components
- Controlled Form do not Hold Data
- Abstract the
handleChangemethod
A controlled form is a form that derives its input values from state.
import React from 'react';
class Form extends React.Component {
state = {
firstName: "John",
lastName: "Henry"
}
render() {
return (
<form>
<input type="text" name="firstName" value={this.state.firstName} />
<input type="text" name="lastName" value={this.state.lastName} />
</form>
)
}
}
export default Form;To completely control a form, we also need our form to update state every time the form changes. Forms should display whatever changes a user makes. For this, we use an event listener onChange that React has set up for us:
<input type="text" onChange={event => this.handleFirstNameChange(event)} value={this.state.firstName} />
<input type="text" onChange={event => this.handleLastNameChange(event)} value={this.state.lastName} />handleFirstNameChange = event => {
this.setState({
firstName: event.target.value
})
}
handleLastNameChange = event => {
this.setState({
lastName: event.target.value
})
}In the handleFirstNameChange() and handleLastNameChange() methods, we're updating state based on event.target.value. This, in turn, causes a re-render... and the cycle completes. The new state values we just set are used to set the value attributes of our two inputs. From a user's perspective, the form behaves exactly how we'd expect, displaying the text that is typed. From React's perspective, we gain control over form values, giving us the ability to more easily manipulate (or restrict) what our inputss display.
Controlling forms makes it more convenient to share form values between components. Since the form values are stored in state, they are easily passed down as props or sent upward via a function supplied in props.
To submit our form we use a second event onSubmit added to the form in JSX.
here is a complete Form Component where though we don't have a server to send our data to, but we modify our Form component to list out submissions, storing them in state:
import React from 'react';
class Form extends React.Component {
state = {
firstName: "John",
lastName: "Henry",
submittedData: []
}
handleFirstNameChange = event => {
this.setState({
firstName: event.target.value
})
}
handleLastNameChange = event => {
this.setState({
lastName: event.target.value
})
}
handleSubmit = event => {
event.preventDefault()
let formData = { firstName: this.state.firstName, lastName: this.state.lastName }
let dataArray = this.state.submittedData.concat(formData)
this.setState({submittedData: dataArray})
}
listOfSubmissions = () => {
return this.state.submittedData.map(data => {
return <div><span>{data.firstName}</span> <span>{data.lastName}</span></div>
})
}
render() {
return (
<div>
<form onSubmit={event => this.handleSubmit(event)}>
<input
type="text"
onChange={event => this.handleFirstNameChange(event)}
value={this.state.firstName}
/>
<input
type="text"
onChange={event => this.handleLastNameChange(event)}
value={this.state.lastName}
/>
<input type="submit"/>
</form>
{this.listOfSubmissions()}
</div>
)
}
}
export default Form;A form, when submitted should send the form data somewhere. As mentioned a moment ago, the traditional HTML way was to send data to a server or another page using the action attribute. In React, we handle requests with asynchronous JavaScript. We won't go into the details of how this works just yet, but we can think of sendFormDataSomewhere() as the code that handles sending our data off.
React provides us with two ways of setting and getting values in form elements. These two methods are called uncontrolled and controlled components. The differences are subtle, but it's important to recognize them — and use them accordingly (spoiler: most of the time, we'll use controlled components).
The quickest way to check if a component is controlled or uncontrolled is to check for value or defaultValue. If the component has a value prop, it is controlled (the state of the component is being controlled by React). If it doesn't have a value prop, it's an uncontrolled component. Uncontrolled components can optionally have a defaultValue prop to set its initial value. These two props (value and defaultValue) are mutually exclusive: a component is either controlled or uncontrolled, but it cannot be both.
The main difference is that in uncontrolled forms the state (the values of 'value=') is internal to the HTML form itself.
All the form data in an uncontrolled form is accessible within the event, but accessing can sometimes be a pain, as you end up writing things like event.target.children[0].value to get the value of our first input.
handleSubmit = event => {
event.preventDefault()
const firstName = event.target.children[0].value
const lastName = event.target.children[1].value
this.sendFormDataSomewhere({ firstName, lastName })
}On a larger form this can turn into some dense code.
In controlled components, we explicitly set the value of a component using state, and update that value in response to any changes the user makes. While it takes a little bit of set up to implement, it makes some other parts of our code easier. For instance, in a basic controlled form, our handleSubmit() function can be relatively simple:
handleSubmit = event => {
event.preventDefault()
this.sendFormDataSomewhere(this.state)
}If our entire state object is just the controlled form data, we can send the entire object around wherever it needs to go. Not only that, if we expanded our form to have 20 controlled inputs, this handleSubmit doesn't change. It just sends all 20 state values wherever we need them to go upon submission.
When we have a controlled form, the state does not need to be stored in the same component. We could store state in a parent component, instead. ParentComponent can maintain all the functions while Form just handles the display of JSX:
// src/components/ParentComponent
import React from 'react';
import Form from './Form'
class ParentComponent extends React.Component {
state = {
firstName: "",
lastName: "",
}
handleFirstNameChange = event => {
this.setState({
firstName: event.target.value
})
}
handleLastNameChange = event => {
this.setState({
lastName: event.target.value
})
}
render() {
return (
<Form
formData={this.state}
handleFirstNameChange={this.handleFirstNameChange}
handleLastNameChange={this.handleLastNameChange}
/>
)
}
}
export default ParentComponent;Then Form can become:
// src/components/Form
import React from 'react';
class Form extends React.Component {
render() {
return (
<div>
<form>
<input
type="text"
onChange={event => this.props.handleFirstNameChange(event)}
value={this.props.formData.firstName}
/>
<input
type="text"
onChange={event => this.props.handleLastNameChange(event)}
value={this.props.formData.lastName}
/>
</form>
</div>
)
}
}
export default Form;NOTE: Submission functionality is omitted here for simplicity. With
ParentComponent, we've moved all the form logic up one level.
Being able to store controlled form data in other components opens some interesting doors for us. We could, for instance, create another component, a sibling of Form, that live displays our form data.
// src/components/DisplayData
import React from 'react';
class DisplayData extends React.Component {
render() {
return (
<div>
<h1>{this.props.formData.firstName}</h1>
<h1>{this.props.formData.lastName}</h1>
</div>
)
}
}
export default DisplayDataAnd adding it alongside Form (also wrapping both in a div:
// src/components/ParentComponent
import React from 'react';
import Form from './Form'
import DisplayData from './DisplayData'
class ParentComponent extends React.Component {
...
render() {
return (
<div>
<Form
formData={this.state}
handleFirstNameChange={this.handleFirstNameChange}
handleLastNameChange={this.handleLastNameChange}
/>
<DisplayData formData={this.state} />
</div>
)
}
}
...Using a controlled component is the preferred way to do things in React — it allows us to keep all component state in the React state, instead of relying on the DOM to retrieve the element's value through its internal state. Using a controlled form, whenever our state changes, the component re-renders, rendering the input with the new updated value.
We can now validate the data the user enters before we set it on the state, allowing us to block any invalid values. If the input is invalid, we simply avoid updating the state, preventing the input from updating. For example, let's say we want to write an input that only takes the numbers 0 through 5.
you can abstract the handleMethod to work with each input, avoiding to clutter the component.
handleChange = event => {
this.setState({
[event.target.name]: event.target.value
})
}here is the full code:
// src/components/ParentComponent
import React from 'react';
import Form from './Form'
import DisplayData from './DisplayData'
class ParentComponent extends React.Component {
state = {
firstName: "",
lastName: "",
}
handleChange = event => {
this.setState({
[event.target.name]: event.target.value
})
}
render() {
return (
<div>
<Form
formData={this.state}
handleChange={this.handleChange}
/>
<DisplayData formData={this.state} />
</div>
)
}
}
export default ParentComponent;
// src/components/Form
import React from 'react';
class Form extends React.Component {
render() {
return (
<div>
<form>
<input
type="text"
name="firstName"
onChange={event => this.props.handleChange(event)}
value={this.props.formData.firstName}
/>
<input
type="text"
name="lastName"
onChange={event => this.props.handleChange(event)}
value={this.props.formData.lastName} />
</form>
</div>
)
}
}
export default Form;
// src/components/DisplayData.js
import React from 'react';
class DisplayData extends React.Component {
render() {
return (
<div>
<h1>{this.props.formData.firstName}</h1>
<h1>{this.props.formData.lastName}</h1>
</div>
)
}
}
export default DisplayData
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ParentComponent from './components/ParentComponent';
ReactDOM.render(
<div>
<ParentComponent/>
</div>,
document.getElementById('root')
);
- Mounting -
static getDerivedStateFromProps(props, state)- Mounting -
componentDidMount()- Updating -
static getDerivedStateFromProps(props, state)- Updating -
shouldComponentUpdate(nextProps, nextState)- Updating -
render()- Updating -
getSnapshotBeforeUpdate(prevProps, prevState)- Updating -
componentDidUpdate(prevProps, prevState, snapshot)- Unmounting -
componentWillUnmount()- Element Ref