iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🤔

[React] When to use uncontrolled components

に公開

Introduction

In this article, I would like to introduce uncontrolled components in React.
They are mainly used to pass values to non-React code by using the DOM as the data source.

What are Uncontrolled Components?

The official React documentation has the following page:
https://ja.reactjs.org/docs/uncontrolled-components.html

The following explanation is provided on that page:

In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.

To write an uncontrolled component, instead of writing an event handler for every state update, you use a ref to get form values from the DOM.

You might have often seen ref itself being used in sample code for external libraries or for specific event operations like focus and scrolling, so many of you likely understand what it is.

In an uncontrolled component, data that would normally be held in state is instead maintained within the DOM using a ref.

Implementation Example

An implementation example is shown below.
Let's assume an ordinary component written in TypeScript called SomeTextInput.tsx, which has an input and a button.

Controlled Component

First is the usual way of writing it.
A component like the one below, which holds state within the component, is generally called a controlled component.

SomeTextInput.tsx
import React from "react";

interface SomeProps {}

interface SomeState {
	text: string;
}

class SomeTextInput extends React.Component<SomeProps, SomeState> {
	/**
	 * Constructor
	 * @param props 
	 */
	constructor(props: SomeProps) {
		super(props);
		// State initialization
		this.state = {
			text: "",
		};
	}

	/**
	 * Input change handler
	 * @param event 
	 */
	onChangeText = (event: React.ChangeEvent<HTMLInputElement>) => {
		this.setState({
			text: event.target.value,
		});
	};

	/**
	 * OK button click handler
	 */
	onClickButton = () => {
		const { text } = this.state;
		console.log(text);
	};

	render = () => {
		const { text } = this.state;
		return (
			<div className="App">
				<input type="text" value={text} onChange={this.onChangeText} />
				<button onClick={this.onClickButton}>OK</button>
			</div>
		);
	};
}

export default SomeTextInput;

Uncontrolled Component

If we rewrite this SomeTextInput.tsx as an uncontrolled component, it will look like this:

SomeTextInput.tsx
import React from "react";

interface SomeProps {}

class SomeTextInput extends React.Component<SomeProps> {

	// ref
	private _input: React.RefObject<HTMLInputElement>;

	/**
	 * Constructor
	 * @param props 
	 */
	constructor(props: SomeProps) {
		super(props);
		this._input = React.createRef<HTMLInputElement>();
	}

	/**
	 * OK button click handler
	 */
	onClickButton = () => {
		console.log(this._input.current.value);
	};

	render = () => {
		return (
			<div className="App">
				<input type="text" ref={this._input} />
				<button onClick={this.onClickButton}>OK</button>
			</div>
		);
	};
}

export default SomeTextInput;

The main change is in the input properties.
Since the input value is directly referenced from the ref within onClickButton, the state is no longer needed, and consequently, onChangeText has been removed.

Why Keep it in the DOM?

The biggest advantage of uncontrolled components is that "you can easily pass values to non-React code."
For example, if only the input form is written in React code and other parts are managed by a different framework, having the values in the DOM allows code that is not React to easily access those values.

Also, as you can see from the sample, since descriptions related to state are gone, the amount of code slightly decreases (though I don't recommend using uncontrolled components just for this purpose).

Cases Where You Can Use Uncontrolled Components

While the advantages of uncontrolled components are as mentioned above, there are several other cases where using uncontrolled components is considered "fine."
*Keep in mind that this just means "there's no problem using them," it doesn't mean they are recommended.

The following article link was in the official documentation:
https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/

Although it is an English article, according to it, the following cases are fine even with uncontrolled components:

  • When submitting form content all at once, such as with a submit button
  • When performing validation of each form value upon submission

This is due to the nature of controlled and uncontrolled components.
Controlled components hold state in state (or store), and onChangeXXX is called every time a value is entered, updating the state each time.
In other words, the state and the screen display are perfectly synchronized.

On the other hand, since uncontrolled components simply have the DOM holding the state, if you want to reflect that content on the screen, you need to actively retrieve the value from the DOM yourself.
You should imagine it as needing to write code that extracts the value once from the DOM, like this._input.current.value in the code, and then reflects it on the screen.

Therefore, in the following examples, uncontrolled components are not recommended because the amount of description and management cost would actually increase:

  • When performing validation synchronously
  • When controlling the screen display based on the input status, such as disabling a button

There are several other examples where they are not recommended, but the main ones are those mentioned above. Since most modern UIs are built this way, the conclusion is basically that it's better to use controlled components.

Summary

In this article, I introduced uncontrolled components with practical examples, their benefits, and cases where they should not be used, along with sample code.

Some articles argue that "uncontrolled components are faster" (perhaps because state doesn't change, so re-rendering is not triggered?), but since the truth of this was unclear, I did not address it here.

I believe they are mainly used for connecting with non-React code, so I hope this serves as a helpful reference for those situations.

Discussion