← Back to Chapters

Uncontrolled Components with useRef

⚛️ Uncontrolled Components with useRef

? Quick Overview

In React, an uncontrolled component is a form element that keeps its own internal state inside the DOM instead of being fully managed by React state.

Instead of syncing values using onChange and value (controlled pattern), you read the current value only when needed using a reference created with useRef().

This approach is handy for simple forms, file inputs, and performance-sensitive scenarios where you do not need real-time validation on every keystroke.

? Key Concepts

  • Controlled Component – Input value is stored in React state and updated via onChange.
  • Uncontrolled Component – Input value lives in the DOM and is accessed through a ref, e.g. inputRef.current.value.
  • useRef() – Hook that gives you a mutable .current object that persists across renders and can point to DOM nodes.
  • defaultValue – Sets the initial value of an uncontrolled input (not tied to state).
  • File Inputs – Commonly handled as uncontrolled because file objects are managed by the browser.
  • Manual Reset – You can clear or modify inputs by updating ref.current.value.

? Syntax & Theory

The core idea is: let the browser manage the input, and only “peek” into its value when you really need it (for example, on form submission).

Steps to build an uncontrolled component with useRef():

  1. Create a ref with const inputRef = useRef();
  2. Attach it to an input: <input ref={inputRef} />
  3. Read its value when needed: inputRef.current.value
? View Code Example
// Basic uncontrolled input using useRef
import React, { useRef } from "react";

function SimpleUncontrolledInput() {
  const inputRef = useRef();

  const handleShowValue = () => {
    alert(`Current value: ${inputRef.current.value}`);
  };

  return (
    <div>
      <input type="text" placeholder="Type here..." ref={inputRef} />
      <button onClick={handleShowValue}>Show Value</button>
    </div>
  );
}

export default SimpleUncontrolledInput;

? Live Behaviour / Explanation

As you type, React does not re-render on each keystroke. When you click Show Value, the current text inside the input is read directly from inputRef.current.value and displayed in an alert.

⚖️ Controlled vs Uncontrolled Components

Here is a conceptual comparison between controlled and uncontrolled components in React:

Aspect Controlled Component Uncontrolled Component
Data Source React state DOM itself
Access Value Via state Via ref.current.value
Use Case Form validation, real-time updates Simple forms, file inputs, performance-critical forms
Code Complexity More verbose Simpler and direct

? Example: Basic Uncontrolled Form

Using useRef(), we can access the input values directly on form submission without storing them in React state.

? View Code Example
// Uncontrolled form that reads values only on submit
import React, { useRef } from "react";

function UncontrolledForm() {
  const nameRef = useRef();
  const emailRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    const name = nameRef.current.value;
    const email = emailRef.current.value;
    alert(`Name: ${name}\nEmail: ${email}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input type="text" ref={nameRef} />
      </div>

      <div>
        <label>Email:</label>
        <input type="email" ref={emailRef} />
      </div>

      <button type="submit">Submit</button>
    </form>
  );
}

export default UncontrolledForm;

? What happens when you run this?

The name and email fields are not bound to React state. When you submit the form, their current values are read via nameRef.current.value and emailRef.current.value, and then displayed in an alert.

? Example: Using Default Values

You can initialize uncontrolled inputs with defaultValue (instead of value). This sets the initial value but does not keep it in sync with React state.

? View Code Example
// Uncontrolled input with a defaultValue
import React, { useRef } from "react";

function DefaultValueForm() {
  const cityRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`City: ${cityRef.current.value}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        defaultValue="Bangalore"
        ref={cityRef}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

export default DefaultValueForm;

? Live Behaviour / Explanation

The input starts with the value Bangalore. The user may change it, and whatever is currently in the box at submit time is read from cityRef.current.value. React does not manage the updates in between.

⚙️ Example: Accessing File Inputs

File inputs are typically handled as uncontrolled components because React cannot directly manage the file objects. Instead, you grab the file from the DOM only when needed.

? View Code Example
// Uncontrolled file uploader using a ref
import React, { useRef } from "react";

function FileUploader() {
  const fileRef = useRef();

  const handleUpload = () => {
    const file = fileRef.current.files[0];
    if (file) {
      alert(`Uploaded File: ${file.name}`);
    } else {
      alert("No file selected!");
    }
  };

  return (
    <div>
      <input type="file" ref={fileRef} />
      <button onClick={handleUpload}>Upload</button>
    </div>
  );
}

export default FileUploader;

? What this code does

When you click Upload, the first selected file is read from fileRef.current.files[0]. Only at that moment do you interact with the file object, keeping the rest of your component logic simple and efficient.

? Example: Resetting Uncontrolled Inputs

You can reset uncontrolled inputs by manually modifying their DOM values via refs instead of resetting React state.

? View Code Example
// Manually clearing an uncontrolled input with a ref
import React, { useRef } from "react";

function ResetForm() {
  const inputRef = useRef();

  const resetField = () => {
    inputRef.current.value = "";
  };

  return (
    <div>
      <input
        type="text"
        ref={inputRef}
        placeholder="Type something..."
      />
      <button onClick={resetField}>Clear</button>
    </div>
  );
}

export default ResetForm;

? Why this is different from controlled inputs

With controlled components, clearing a field usually means setting its state to an empty string. Here, you directly modify inputRef.current.value, and the DOM updates instantly without any state change or re-render.

? When to Use Uncontrolled Components

  • ✅ For simple forms where tracking input state isn’t necessary.
  • ✅ For forms managed by third-party libraries or legacy code.
  • ✅ For <input type="file"> or password fields.
  • ✅ When performance is a priority and re-rendering should be minimized.

However, uncontrolled components are not ideal when you need:

  • Real-time validation or error messages on every keystroke.
  • Conditional rendering based on the current input value.
  • Complex form logic where multiple fields depend on shared React state.

? Tips & Best Practices

  • Use useRef() primarily for DOM access, not for reactive data that should trigger renders.
  • defaultValue initializes input values without making them controlled.
  • Combine controlled and uncontrolled inputs carefully—know which fields truly need to be in React state.
  • Prefer controlled inputs for validation-heavy forms and complex logic flows.
  • Keep uncontrolled components for simple “read-only-on-submit” style forms or file uploads.

? Try It Yourself / Practice Tasks

  1. Create a simple registration form (name, email, password) using useRef() for all inputs and show the values in an alert on submit.
  2. Add a “Show Data” button that reads the current values from refs and logs them to the console without using state.
  3. Build a small file upload form using refs to display the names of all selected files before upload.
  4. Implement the same registration form twice: once with controlled components and once with uncontrolled components. Compare:
    • How often each component re-renders
    • The amount of code you write
    • How easy validation is in each version

Goal: Understand how to use useRef() to manage DOM elements directly for forms and when uncontrolled components are a better fit than controlled ones.