Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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>
like image 222
Donuts Avatar asked Mar 05 '17 09:03

Donuts


People also ask

How do you fix a component is changing an uncontrolled input to be controlled?

The warning "A component is changing an uncontrolled input to be controlled" occurs when an input value is initialized to undefined but is later changed to a different value. To fix the warning, initialize the input value to an empty string, e.g. value={message || ''} .

How do you make an uncontrolled component in React?

To write an uncontrolled component, instead of writing an event handler for every state update, you can use a ref to get form values from the DOM. Since an uncontrolled component keeps the source of truth in the DOM, it is sometimes easier to integrate React and non-React code when using uncontrolled components.

What is uncontrolled input in React?

What are uncontrolled components in React? Uncontrolled components are those for which the form data is handled by the DOM itself. “Uncontrolled” refers to the fact that these components are not controlled by React state. The values of the form elements are traditionally controlled by and stored on the DOM.

What is controlled input and uncontrolled input?

In a controlled component, form data is handled by a React component. Whereas in uncontrolled components, form data is handled by the DOM itself. Usage of Component State is a must for controlled components. Use of state is completely optional for uncontrolled components, but one must use Refs in it.


2 Answers

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 || ''}/>
like image 163
Simon Boudrias Avatar answered Oct 24 '22 09:10

Simon Boudrias


The underlying problem is that the way I'm setting a property on the shipping object, it does not merge the new property value with the original property values. Therefore the warning wasn't for the TextField that i was EDITING, it was for the other shipping TextFields that were getting blown away. I'm not sure if this is accepted practice since it's hard to find examples where you're working with an object in the state. However, changing the handleShipping method to this fixed my problem:

handleShippingChange(event) {
  var shipping = this.state.shipping;
  shipping[event.target.name] = this.getFieldValue(event.target);

  this.setState({
    shipping: shipping
  });
};

Basically, I'm creating a copy of the existing shipping object from the state, altering it and setting the entire shipping object equal to the altered copy.

like image 41
Donuts Avatar answered Oct 24 '22 10:10

Donuts