Controlled Component in React Forms

Form controls in React come in two types: Controlled Component and Uncontrolled Component. In this video, we will focus only on the Controlled Component.

In a Controlled Component, the “single source of truth” (data) is maintained in the React state property.    An input form element whose value is controlled by React in this way is called a “controlled component”.

In HTML, form elements such as <input><textarea>, and <select> typically maintain their own state and update it based on user input. In React, mutable state is typically kept in the state property of components, and only updated with setState().

In a Controlled Component, form data is handled by a React component using an event handler (e.g. handleChange) for every state update.  Since handleChange runs on every keystroke to update the React state, the displayed value will update as the user types.  This makes it straightforward to modify or validate user input. The following example illustrates this explanation.

Source code for demo in the video

import React from 'react';

//Parent component - contains main data
class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = { data: [] };
        this.addData = this.addData.bind(this);
    }

    addData(newData){
        let data = this.state.data;
        data.push(newData);
        this.setState({ data });
    }

    render() {
        let data = this.state.data;
        return (
            <div className="col-lg-12 text-center">
            <h1>React Forms - Controlled Component</h1>  
            <AddForm addData={this.addData} data={this.state.data}></AddForm>  
            <hr></hr>
            <ul>
                {data.map( (e,i)=>{
                    return (
                        <li key={i}>
                            {e.user} | {e.game} | {e.points}
                        </li>
                    )
                })

                }
            </ul>            
            </div>
        );
    }
}

// Child component - the Form to add one record
// Controlled Component - source of truth in React
class AddForm extends React.Component{
    constructor(props){
        super(props);
        this.state = { data : {user:'',game:'Galaga',points:0}}
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }
    
    handleChange(event){
        let {name, value} = event.target;
        let data = this.state.data;
        data[name] = value;
        this.setState({data})
    }
    error=false;
    handleSubmit(event){
        //validate
        event.preventDefault();
        if(!this.error){
            this.props.addData(this.state.data);
            this.setState({ data : {user:'',game:'Galaga',points:0}});
        }
    }

    render(){
        let data = this.state.data;
        return(
            <div className="col-md-8 ml-5">
                <form>
                    <div className="form-row">
                        <div className="col-md-4 mb-3">
                            <label>User:</label>  
                            <input 
                                className="form-control"
                                name="user"
                                value={data.user}
                                onChange={this.handleChange}
                                />  
                        </div>
                        <div className="col-md-4 mb-3">
                            <label>Game:</label>
                            <select 
                                className="form-control"
                                name="game"
                                defaultValue={data.game}
                                onChange={this.handleChange}
                                >
                                <option>Pac-Man</option>
                                <option>Pong</option>
                                <option>Galaga</option>
                                <option>Tetris</option>
                            </select>
                        </div>
                        <div className="col-md-4 mb-3">
                            <label>Points:</label>
                            <input 
                                className="form-control"
                                name="points"
                                type="number"
                                min='0'
                                max='10000'
                                value={data.points}
                                onChange={this.handleChange}
                                /> 
                        </div>
                        <div className="col-md-4 mb-3">
                            <button onClick={this.handleSubmit} className="btn btn-primary">Submit</button>
                        </div>
                    </div>
                </form>
            </div>
        )
    }
}

export default App;