React.js Controlled Form Components: Comprehensive Guide and Code Examples
This article provides a detailed walkthrough of building controlled form components in React.js, covering text, number, checkbox, radio, textarea, and select inputs, along with state management, form submission, clearing, validation, and full source code examples.
The article introduces the concept of controlled components in React.js and explains why real‑world examples for inputs such as checkboxes, radio buttons, and select boxes are often missing from tutorials.
What is a controlled component? A controlled component stores its value in the parent’s state and updates that state on every onChange event, enabling a single‑direction data flow from child to parent and back via props.
Form Structure
The top‑level App component simply renders the FormContainer component inside a container div:
import React, { Component } from 'react';
import '../node_modules/spectre.css/dist/spectre.min.css';
import './styles.css';
import FormContainer from './containers/FormContainer';
class App extends Component {
render() {
return (
React.js Controlled Form Components
);
}
}
export default App;Container (Smart) Component
The FormContainer holds all form state, fetches initial data in componentDidMount , and defines handler methods for each field (e.g., handleFullNameChange , handlePetSelection , handleClearForm , handleFormSubmit ).
import React, { Component } from 'react';
import CheckboxOrRadioGroup from '../components/CheckboxOrRadioGroup';
import SingleInput from '../components/SingleInput';
import TextArea from '../components/TextArea';
import Select from '../components/Select';
class FormContainer extends Component {
constructor(props) {
super(props);
this.state = {
ownerName: '',
petSelections: [],
selectedPets: [],
ageOptions: [],
ownerAgeRangeSelection: '',
siblingOptions: [],
siblingSelection: [],
currentPetCount: 0,
description: ''
};
// bind handlers
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.handleClearForm = this.handleClearForm.bind(this);
this.handleFullNameChange = this.handleFullNameChange.bind(this);
this.handleCurrentPetCountChange = this.handleCurrentPetCountChange.bind(this);
this.handleAgeRangeSelect = this.handleAgeRangeSelect.bind(this);
this.handlePetSelection = this.handlePetSelection.bind(this);
this.handleSiblingsSelection = this.handleSiblingsSelection.bind(this);
this.handleDescriptionChange = this.handleDescriptionChange.bind(this);
}
componentDidMount() {
fetch('./fake_db.json')
.then(res => res.json())
.then(data => {
this.setState({
ownerName: data.ownerName,
petSelections: data.petSelections,
selectedPets: data.selectedPets,
ageOptions: data.ageOptions,
ownerAgeRangeSelection: data.ownerAgeRangeSelection,
siblingOptions: data.siblingOptions,
siblingSelection: data.siblingSelection,
currentPetCount: data.currentPetCount,
description: data.description
});
});
}
handleFullNameChange(e) { this.setState({ ownerName: e.target.value }); }
handleCurrentPetCountChange(e) { this.setState({ currentPetCount: e.target.value }); }
handleAgeRangeSelect(e) { this.setState({ ownerAgeRangeSelection: e.target.value }); }
handlePetSelection(e) {
const newSelection = e.target.value;
let newSelectionArray;
if (this.state.selectedPets.indexOf(newSelection) > -1) {
newSelectionArray = this.state.selectedPets.filter(s => s !== newSelection);
} else {
newSelectionArray = [...this.state.selectedPets, newSelection];
}
this.setState({ selectedPets: newSelectionArray });
}
handleSiblingsSelection(e) { this.setState({ siblingSelection: [e.target.value] }); }
handleDescriptionChange(e) { this.setState({ description: e.target.value }); }
handleClearForm(e) {
e.preventDefault();
this.setState({
ownerName: '',
selectedPets: [],
ownerAgeRangeSelection: '',
siblingSelection: [],
currentPetCount: 0,
description: ''
});
}
handleFormSubmit(e) {
e.preventDefault();
const formPayload = {
ownerName: this.state.ownerName,
selectedPets: this.state.selectedPets,
ownerAgeRangeSelection: this.state.ownerAgeRangeSelection,
siblingSelection: this.state.siblingSelection,
currentPetCount: this.state.currentPetCount,
description: this.state.description
};
console.log('Send this in a POST request:', formPayload);
this.handleClearForm(e);
}
render() {
return (
Pet Adoption Form
Clear form
);
}
}
export default FormContainer;Individual Controlled Components
SingleInput renders a label and an input whose type can be text or number . PropTypes enforce required props such as inputType , title , name , controlFunc , content , and optional placeholder .
import React from 'react';
const SingleInput = (props) => (
{props.title}
);
SingleInput.propTypes = {
inputType: React.PropTypes.oneOf(['text', 'number']).isRequired,
title: React.PropTypes.string.isRequired,
name: React.PropTypes.string.isRequired,
controlFunc: React.PropTypes.func.isRequired,
content: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]).isRequired,
placeholder: React.PropTypes.string,
};
export default SingleInput;Select renders a select element with a placeholder option and maps an array of options to option tags. It receives name , options , selectedOption , controlFunc , and optional placeholder .
import React from 'react';
const Select = (props) => (
{props.placeholder}
{props.options.map(opt => (
{opt}
))}
);
Select.propTypes = {
name: React.PropTypes.string.isRequired,
options: React.PropTypes.array.isRequired,
selectedOption: React.PropTypes.string,
controlFunc: React.PropTypes.func.isRequired,
placeholder: React.PropTypes.string,
};
export default Select;CheckboxOrRadioGroup renders a group of checkboxes or radio buttons based on the type prop. It uses props.options.map to generate each input and determines the checked state by searching props.selectedOptions .
import React from 'react';
const CheckboxOrRadioGroup = (props) => (
{props.title}
{props.options.map(opt => (
-1}
type={props.type}
/> {opt}
))}
);
CheckboxOrRadioGroup.propTypes = {
title: React.PropTypes.string.isRequired,
type: React.PropTypes.oneOf(['checkbox', 'radio']).isRequired,
setName: React.PropTypes.string.isRequired,
options: React.PropTypes.array.isRequired,
selectedOptions: React.PropTypes.array,
controlFunc: React.PropTypes.func.isRequired,
};
export default CheckboxOrRadioGroup;TextArea renders a textarea with configurable rows, optional resize control, and standard props for label, name, content, placeholder, and change handler.
import React from 'react';
const TextArea = (props) => (
{props.title}
);
TextArea.propTypes = {
title: React.PropTypes.string.isRequired,
rows: React.PropTypes.number.isRequired,
name: React.PropTypes.string.isRequired,
content: React.PropTypes.string.isRequired,
resize: React.PropTypes.bool,
placeholder: React.PropTypes.string,
controlFunc: React.PropTypes.func.isRequired,
};
export default TextArea;Form Operations
The handleClearForm method resets all state fields to empty strings, empty arrays, or zero, preventing the default form submission behavior. The handleFormSubmit method gathers the current state into a payload object, logs it (representing a POST request), and then clears the form.
Validation Example
A simple validation is demonstrated for the TextArea component: the handler splits the input string into characters, filters out the letter “e”, rejoins the array, and updates state, effectively preventing the user from entering that character.
handleDescriptionChange(e) {
const textArray = e.target.value.split('').filter(x => x !== 'e');
const filteredText = textArray.join('');
this.setState({ description: filteredText });
}Overall, the article shows that while creating controlled form components in React requires some repetitive boilerplate (especially in the container component), the approach yields highly maintainable, predictable, and testable UI code.
Hujiang Technology
We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.