1. Code
  2. JavaScript

Learn React 18: Using Uncontrolled Inputs

Scroll to top
60+ min read

In our previous tutorial, we learned how to use controlled inputs in React. Another way to manage form data in React is with the help of uncontrolled inputs.

In this lesson from our free full-length beginner course on React 18, you'll learn how. 

Adding an Uncontrolled Input

When using a controlled input, we were responsible for setting and updating the values of our form input elements. This meant that we had to create onChange listeners for all these events to update values. We also used the value attribute to set the value for each input element.

With uncontrolled inputs, we don't have to worry about setting or updating input values, so there is no need to create onChange event listeners or use a value attribute. Here is the code for our render() method with these things in mind.

1
render() {
2
    return (
3
      <div class="container">
4
        <h1>Random Number Generator</h1>

5
        <div className="range-container">
6
        <p>Lower Limit: {this.state.lowerLimit}</p>

7
        <p>Upper Limit: {this.state.lowerLimit + this.state.numberRange}</p>

8
        </div>

9
        <h2>{this.state.randomNumber}</h2>

10
        <form>
11
          <input type="number" ref={this.lowInput} />

12
          <input type="number" ref={this.rangeInput} />

13
          <button type="submit" onClick={this.handleSubmission}>Submit</button>

14
        </form>

15
      </div>

16
    );
17
}

We have added two input elements and a button to our form here. The input elements will dictate the values of the lower limit and output range for our random number. The button has an onClick event listener that will handle the form submission and update the random number parameters.

Even though we are no longer in charge of setting and updating input values, we still need to access them to update the parameters of our random number generator. We get this access with Refs. Refs are used to store references to different elements inside the component. We can create a Ref by using React.createRef(). Here is the code for our constructor method where we create these Refs.

1
constructor(props) {
2
    super(props);
3
    
4
    this.rangeInput = React.createRef();
5
    this.lowInput = React.createRef();
6
7
    this.state = {
8
      numberRange: props.numRange,
9
      lowerLimit: props.low,
10
      randomNumber: Math.floor(Math.random() * props.numRange) + props.low
11
    };
12
    
13
    this.handleSubmission = this.handleSubmission.bind(this);
14
}

As I said earlier, we create Refs for our input elements inside the constructor. The state property now also includes a lowerLimit value to store the value of our lowInput input element. We also bind the handleSubmission() method to our class inside the constructor.

The handleSumbission() method will take the values from our input and sanitize them. After that, the values are stored in the component's state. We also bring the focus back to our lowInput element for a better user experience.

1
handleSubmission(e) {
2
    e.preventDefault();
3
    
4
    let newRange = parseInt(this.rangeInput.current.value);
5
    let newLow = parseInt(this.lowInput.current.value);
6
    
7
    
8
    if(isNaN(newRange) || newRange < 0) {
9
      newRange = 0;
10
    }
11
    
12
    if(isNaN(newLow) || newLow < 0) {
13
      newLow = 0;
14
    }
15
    
16
    this.setState({numberRange: newRange, lowerLimit: newLow});
17
    
18
    this.lowInput.current.focus();
19
}

As you can see, we use the current attribute of our refs in order to get access to different elements.

Now it's time to update our componentDidMount() method so that it uses the newly set limits to create the random numbers.

1
componentDidMount() {
2
    this.numTimer = setInterval(() => {
3
      let tempVal = Math.floor(Math.random() * this.state.numberRange) + this.state.lowerLimit;
4
      this.setState({ randomNumber: tempVal });
5
    }, 2000);
6
}

The following CodePen demo shows our RandomGenerator component using uncontrolled inputs in action.

We have seen how controlled and uncontrolled inputs are different from each other from a developer's standpoint. These inputs also behave differently from a user's perspective.

Let's say you enter a non-numeric value in the input fields above. You will see that the input field actually displays the value. However, the lower limit value in the component's state will adjust to zero without changing the value of the input field. This is because we are not controlling the input here. On the other hand, try entering a non-numeric value in the input field of the previous tutorial, and you will see that it updates not only the component state but also the input value. This is because we were in control of the input there.

Another thing that you will notice here is that we were able to take the focus back to our first input element while handling the form submission. It was possible because we have a reference to the input element and could call focus() to bring it into focus. We were not able to do that in the previous tutorial because we did not have a reference to the element.

For practice, you should try to modify the above component so that it uses controlled inputs. Keep in mind that you will have to add onChange event listeners for both inputs to do so.

Final Thoughts

I hope this tutorial helped you understand the difference between controlled and uncontrolled inputs. Controlled inputs require you to write more code to handle any changes in input values. However, they are considered the correct approach because they allow you to have a single source of truth in your components.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.