Reactjs – React form error changing a controlled input of type text to be uncontrolled

reactjs

I'm creating a simple form with react using controlled inputs. In my component state, I have 2 properties "clientName" and "license" on state. changing those works fine. But there's a "shipping" property that is an object. Changing any of the shipping properties gives an error. For example, if i change "address1", as soon as the state is set in the handleShippingChange function, I get the error:

Warning: TextField is changing a controlled input of type text to be
uncontrolled. Input elements should not switch from controlled to
uncontrolled (or vice versa). Decide between using a controlled or
uncontrolled input element for the lifetime of the component.

I suspect it has something to do with how I'm defining the value for those TextFields and how I'm setting the state for shipping properties. What am I doing wrong? The code for my component is below:

import React, {Component} from 'react';
    import TextField from 'material-ui/TextField';
    import RaisedButton from 'material-ui/RaisedButton';
    import 'whatwg-fetch';

    class Clients extends Component {
      constructor() {
        super();
        this.state = {
          "clientName": "client name",
          "shipping": {
            "name": "name",
            "address1": "address 1",
            "address2": "address 2",
            "city": "city",
            "state": "state",
            "zip": "zip",
            "country": "country"
          },
          "license": "license"
        };

        this.handleChange = this.handleChange.bind(this);
        this.handleShippingChange = this.handleShippingChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      };       

      handleChange(event) {
        this.setState({
          [event.target.name]: this.getFieldValue(event.target)
        });
      };

      handleShippingChange(event) {
        this.setState({
          shipping: {
            [event.target.name]: this.getFieldValue(event.target)
          }
        });
      };

      getFieldValue(target) {
        return target.type === 'checkbox' ? target.checked : target.value;
      };

      handleSubmit = (event) => {
        event.preventDefault();

        // do some stuff
      };   

      render() {
        return <div>
          <h1>
            Clients Page
          </h1>

          <form onSubmit={this.handleSubmit}>
            <TextField
              hintText="Enter the client name"
              floatingLabelText="Client Name"
              value={this.state.clientName}
              onChange={this.handleChange}
              name="clientName"
            />
            <h2>Shipping Info</h2>
            <TextField
              hintText=""
              floatingLabelText="Name"
              value={this.state.shipping.name}
              onChange={this.handleShippingChange}
              name="name"
            />
            <br />
            <TextField
              hintText=""
              floatingLabelText="Address Line 1"
              value={this.state.shipping.address1}
              onChange={this.handleShippingChange}
              name="address1"
            />
            <br />
            <TextField
              hintText=""
              floatingLabelText="Address Line 2"
              value={this.state.shipping.address2}
              onChange={this.handleShippingChange}
              name="address2"
            />
            <br />
            <TextField
              hintText=""
              floatingLabelText="City"
              value={this.state.shipping.city}
              onChange={this.handleShippingChange}
              name="city"
            />
            <br />
            <TextField
              hintText=""
              floatingLabelText="State"
              value={this.state.shipping.state}
              onChange={this.handleShippingChange}
              name="state"
            />
            <br />
            <TextField
              hintText=""
              floatingLabelText="Zip Code"
              value={this.state.shipping.zip}
              onChange={this.handleShippingChange}
              name="zip"
            />
            <br />
            <TextField
              hintText=""
              floatingLabelText="Country"
              value={this.state.shipping.country}
              onChange={this.handleShippingChange}
              name="country"
            />
            <br />
            <TextField
              hintText=""
              floatingLabelText="License"
              value={this.state.license}
              onChange={this.handleChange}
              name="license"
            />
            <br />
            <RaisedButton label="OK" primary={true} type="submit" />
          </form>
        </div>
      };
    }

    export default Clients;
<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>

Best Answer

Controlled/Uncontrolled input means if the <input> field has a value or not.

// This is a controlled input
<input value="foo"/>

// This is an uncontrolled input
<input value={null}/>

The idea is that you don't want to change from a controlled to an uncontrolled input. Both types of input acts differently and this could potentially lead to bugs and/or inconsistency.

The easiest fix is to ensure there's always a default value (in the case of an empty field, the default would be an empty string '').

Also, note that consistent types are usually better than nullable types given you have a guarantee on the type of a certain value. This helps a lot in reducing overhead due to null checks (if (val != null) { /* ...etc */ })

But if you just want a one line fix, you can also provide the default value inline in jsx:

<input value={value || ''}/>
Related Topic