Understanding Controlled vs Uncontrolled Components In React

Updated on · 7 min read
Understanding Controlled vs Uncontrolled Components In React

When building forms or handling input fields in React applications, developers often encounter the terms "controlled" and "uncontrolled" components. These concepts are fundamental to understanding how React handles user input and form states. Controlled components are those where form data is handled by the React component state, offering a single source of truth and enabling more complex interactions and validations. On the other hand, uncontrolled components rely on the DOM itself to manage form data, providing a more traditional HTML-like approach. Choosing the right type of component is crucial for maintaining code quality, scalability, and ease of development.

In this post, we'll look at the specifics of controlled versus uncontrolled components in React: how they work, their respective advantages, and guidelines for choosing the right approach for various scenarios in React applications. We'll also explore how to transition between the two types of components and provide code examples to illustrate the concepts.

Controlled components

Controlled components allow React to maintain the form data. This happens via the use of state within the component. Whenever there is a change in the form element, an event handler is invoked that manages the change and updates the component's state. As the state updates, so does the component, ensuring the data displayed is always in sync with the state of the component.

To implement a controlled component, you should:

  1. Initialize the state in the constructor or use state hooks (useState) for function components.
  2. Set the value of the form element to a corresponding state variable.
  3. Create an event handler that will handle user input changes - this function uses setState or the updater function from useState to update the component's state whenever there is an input change.
  4. Attach this event handler to the form element's 'onChange' event.

An example implementation for a controlled <input> element used in forms would look like this:

jsx
import React, { useState } from "react"; function LoginForm() { const [username, setUsername] = useState(""); const handleChange = (event) => { setUsername(event.target.value); }; return ( <form> <label> Username: <input type="text" value={username} onChange={handleChange} /> </label> </form> ); }
jsx
import React, { useState } from "react"; function LoginForm() { const [username, setUsername] = useState(""); const handleChange = (event) => { setUsername(event.target.value); }; return ( <form> <label> Username: <input type="text" value={username} onChange={handleChange} /> </label> </form> ); }

Controlled components come with several advantages. They enable developers to:

  • Implement instant field validation as the user types or when the field is blurred.
  • Integrate state management tools and libraries seamlessly.
  • Utilize the powerful React ecosystem, which includes features like Hooks, for more straightforward state management.
  • Enforce input formats, by controlling what the user can type into an input field.

Moreover, making use of controlled inputs allows React to act as the "single source of truth", which aligns with React's design philosophy and can lead to more predictable and easier-to-debug code.

In summary, controlled components are more declarative and align well with React's way of handling component states and reactivity, providing a robust mechanism to handle user input and form data.

Uncontrolled components

Uncontrolled components are another way to handle form inputs in React. Instead of writing an event handler for all your state updates, uncontrolled components allow you to use refs to access the DOM nodes directly. In uncontrolled components, you treat form elements as sources of truth, meaning that you retrieve the form values directly from the DOM nodes rather than from the React component's state. Using refs, you can easily access the form elements' underlying DOM node, making uncontrolled components closer in nature to traditional HTML form elements. This is how React Hook Form works under the hood when managing its uncontrolled components via the register function.

Implementing an uncontrolled component involves the following steps:

  1. Create a ref using React.createRef() in a class component or useRef() hook in a function component.
  2. Attach this ref to the form element.
  3. Interact with the form element's value as needed directly through this ref, sparingly in the lifecycle methods like componentDidMount for class components, or inside event handlers.

Here's an example of an uncontrolled component using useRef in a function component:

jsx
import React, { useRef } from "react"; function LoginForm() { const usernameRef = useRef(null); const handleSubmit = (event) => { event.preventDefault(); alert("A username was submitted: " + usernameRef.current.value); }; return ( <form onSubmit={handleSubmit}> <label> Username: <input type="text" ref={usernameRef} /> </label> <button type="submit">Submit</button> </form> ); }
jsx
import React, { useRef } from "react"; function LoginForm() { const usernameRef = useRef(null); const handleSubmit = (event) => { event.preventDefault(); alert("A username was submitted: " + usernameRef.current.value); }; return ( <form onSubmit={handleSubmit}> <label> Username: <input type="text" ref={usernameRef} /> </label> <button type="submit">Submit</button> </form> ); }

In case you are using TypeScript, it's important to properly type the ref to avoid potential type issues.

Some advantages of using uncontrolled components include:

  • Slightly less code for managing form state, which can be more straightforward for simple forms.
  • More similar to traditional HTML form behaviors, which can be comfortable for developers transitioning from non-React environments.
  • Potentially less re-rendering since the state is not updated for every input change, which can be better for performance in some scenarios.

In essence, uncontrolled components provide a more straightforward but less "React-centric" way to work with form inputs, striking a balance between the native HTML experience and the reactivity that React offers. They can significantly simplify certain use cases, though the use of refs must be handled with care to maintain proper React paradigms and application integrity.

Comparing controlled and uncontrolled components

When deciding between controlled and uncontrolled components, understanding their differences and the trade-offs involved is important.

The core difference between controlled and uncontrolled components lies in how they handle and store their state. Controlled components rely on React to manage the state, whereas uncontrolled components use the actual DOM to manage the state. The implication of this distinction extends to how changes are monitored and how the component data is extracted.

Controlled components may lead to more render cycles because every change to the input triggers a state update and a consequent re-render of the component. This can impact performance, especially in large forms or when working on lower-powered devices. Uncontrolled components sidestep this by not linking state changes to input updates, thus reducing the number of re-renders.

In scenarios where real-time validation or enabling/disabling of form elements based on input is needed, controlled components shine due to their tight integration with the component's state. For simpler forms without complex logic or for quick prototyping, uncontrolled components are often more than sufficient and can allow for a more straightforward implementation.

Ultimately, the choice between controlled and uncontrolled components will come down to the specific needs of the application and the complexity of the form handling required.

Transitioning between controlled and uncontrolled components

There might be instances where you start with one approach and later realize that another would be more beneficial for your application. It's essential to know how to transition between controlled and uncontrolled components without causing significant refactoring headaches.

Transitioning from an uncontrolled to a controlled component often involves introducing a state variable and onChange handler and then binding the input's value to this state. Conversely, switching to an uncontrolled component from a controlled one typically requires removing the state bindings and introducing a ref to access the input's value. Here are points to consider during the transition:

  1. Evaluate the reason for transition: ensure that the need to transition is driven by sound reasons related to functionality, performance, or code maintainability.
  2. Update the component's structure: for controlled components, bind the input values to state variables. For uncontrolled components, set up refs to access form element values directly.
  3. Refactor event handlers: controlled components will need handlers for updating state, while uncontrolled components might only need handlers for form submission or similar actions.
  4. Test extensively: changing how state is handled in a component can introduce bugs; thorough testing is crucial.

Transitioning between controlled and uncontrolled patterns can lead to a mixture of patterns, which can cause confusion and errors. One pitfall includes mixing refs with state management, leading to split sources of truth. Clear guidelines and best practices should be established to avoid these pitfalls.

Conclusion

Controlled and uncontrolled components in React each offer unique advantages that cater to different scenarios. Controlled components provide a high level of control over the user input and form state, making them suitable for complex scenarios involving dynamic forms and real-time validation. On the other hand, uncontrolled components offer a simpler approach that might result in performance enhancements in specific use cases, particularly when dealing with large forms or form interactions that don't require granular control of the state.

In choosing between controlled and uncontrolled components, it is necessary to consider the nature of the form, the complexity of its behavior, and performance implications. Sometimes, you may even find a need to switch from one approach to the other as application requirements evolve.

Understanding the differences between controlled and uncontrolled components and knowing how to transition between them is crucial for building scalable and maintainable React applications. By leveraging the strengths of each approach, developers can build forms that are both performant and easy to maintain, ensuring a smooth user experience and a robust application.

References and resources