Understanding and Fixing Uncontrolled to Controlled Input Warnings in React

John Muriithi - Jul 10 - - Dev Community

Encountering the 'A component is changing an uncontrolled input to be controlled' and vice versa warning in React can be perplexing for both beginners and experienced developers. This article delves into the causes of this common error and provides practical solutions to ensure smooth input state management in your React applications.

Before fixing this warning, let's first understand what is Uncontrolled and Controlled input elements

In React, form inputs can be either controlled or uncontrolled.

1. Controlled Inputs
These are inputs where the value is controlled by the React state. Meaning the value of this input has to 'live' somewhere.The value of the input field is bound to the components state [ the component rendering the Form ] and changes to the input field update the state

import { useState } from 'react'

export default function App() {
    const [name, setName] = useState("");

    const handleName = (event) => {
        setName(event.target.value)
    }

  return (
    <div>
      <input value={name} onChange={handleName} type='text' />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Form elements becomes "Controlled" if you set it's value via prop. That's All!

2. Uncontrolled Components
These are inputs where the value is managed by the DOM itself.They remember what you typed.You therefore can retrieve the value only when needed, using a ref. In other words, the source of of truth is the DOM. You have to 'pull' the value from the field when you need it.This can happen when the form is submitted or by a click of a button etc.

import { useRef } from 'react'

export default function App() {
    const input = useRef("");

    const handleInput = () => {
      alert(input.current.value);
    }

  return (
    <div>
      <form onSubmit={handleInput}>
        <input type='text' ref={input} />
        <button type='submit'>Submit</button> {/* when we feel like submitting any time, the input value typed will be remembered */}
      </form>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Hence the warning means that an input component started as uncontrolled (not having a value controlled by React) and then switched to being controlled (having a value controlled by React state) or vice versa.

Why does this error occur / what you may doing wrong in your code

1. Initial State Set to undefined or null:

If the initial state for a controlled input is set to undefined or null, React treats it as uncontrolled. When the state updates to a defined value, React sees this switch and throws the warning.

Setting the initial state to undefined or null makes the input start as uncontrolled. React does not have a value to bind to the input, so it defaults to letting the DOM manage it. When the state changes to a defined value later, it becomes controlled, causing the warning.

Example Code ( will definetly throw the warning ):

import { useState, useEffect } from 'react'; 

function App () {
    const [inputValue, setInputValue] = useState(undefined); {/* this is a controlled input but react treats it as uncontrolled because we are setting initial state to undefined */}

    useEffect(() => {
        // here we are just Simulating an async operation
        setTimeout(() => {
            setInputValue('Initial Value'); {/* here we are updating the state to a defined value, React sees this switch and will trow the warning */}
        }, 2000);
    }, []);

    return (
        <input
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
        />
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

The input starts without a value prop, so it's uncontrolled. Once value is set to a defined value (e.g., a string), the input becomes controlled, triggering the warning.

Solution

Ensure that the state used for the input value is initialized properly. For controlled inputs, it should have a defined initial value (usually an empty string for text inputs).

Initializing the state with an empty string makes the input start as controlled. React always has a value to bind to the input, even if it's empty. This means the input is controlled from the beginning, avoiding the switch from uncontrolled to controlled.

import { useState, useEffect } from 'react';

function App () {
    const [inputValue, setInputValue] = useState(""); {/* React treats this as controlled input, good to go */}

    useEffect(() => {
        // here we are just Simulating an async operation
        setTimeout(() => {
            setInputValue('Initial Value'); {/* the input expects this ( value to be set via prop ), good to go */}
        }, 2000);
    }, []);

    return (
        <input
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
        />
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

The input is controlled from the start, as it has a value (""). Any changes to the input's value are managed through React state, ensuring consistent behavior without warnings.

2. Improper State Management

The state managing the value of the input might not be initialized properly or could be changing in ways that cause the input to switch modes.

Please note, the code below useState() is not initialized properly, this will cause input to switch modes

import { useState, useEffect } from 'react';

function App () {
    const [inputValue, setInputValue] = useState(); // here no set initial state (will throw the warning)

    useEffect(() => {
        // here we are just Simulating an async operation
        setTimeout(() => {
            setInputValue('Initial Value');
        }, 2000);
    }, []);

    return (
        <input
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
        />
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Solution:

Initialize the value of the input properly e.i useState("") etc

3. Conditional Rendering

If the value prop of an input is conditionally rendered and sometimes ends up being undefined, the input can unintentionally switch from uncontrolled to controlled.

Example Code:

import React, { useState } from 'react';

function ConditionalInputComponent() {
    const [inputValue, setInputValue] = useState('Initial Value');
    const [showInput, setShowInput] = useState(true);

    return (
        <div>
            {showInput && (
                <input
                    type="text"
                    value={inputValue}
                    onChange={(e) => setInputValue(e.target.value)}
                />
            )}
            <button onClick={() => setShowInput(!showInput)}>
                Toggle Input
            </button>
        </div>
    );
}

export default ConditionalInputComponent;

Enter fullscreen mode Exit fullscreen mode

Solution:
When conditionally rendering inputs, make sure the value prop is never set to undefined or null. Use an empty string or some default value instead.
Ensure the value prop is always a defined value when the input is rendered.

function ConditionalInputComponent() {
    const [inputValue, setInputValue] = useState('Initial Value');
    const [showInput, setShowInput] = useState(true);

    return (
        <div>
            {showInput ? (
                <input
                    type="text"
                    value={inputValue}
                    onChange={(e) => setInputValue(e.target.value)}
                />
            ) : (
                <input type="text" value="" readOnly />
            )}
            <button onClick={() => setShowInput(!showInput)}>
                Toggle Input
            </button>
        </div>
    );
}

Enter fullscreen mode Exit fullscreen mode

Consistency is Key:

Ensure that the input does not switch between controlled and uncontrolled modes. Pick one approach and stick with it for the lifecycle of the component.

Key Questions to Ask When Debugging
When faced with this warning, consider the following questions:

1. What is the Initial State of the Input?
Is it undefined or null? If so, you should initialize it to an empty string or a valid value.

2. Is the Input Value Conditional?
Are you conditionally rendering or setting the input value? Ensure it doesn’t switch between defined and undefined states.

3. Are You Using React Refs Correctly?
For uncontrolled inputs, ensure you are using React refs and not managing the state directly with React state.

4. What is the Source of the Value?
Is the value prop tied directly to the state? If not, make sure it is consistent.

Conclusion

The warning "A component is changing an uncontrolled input to be controlled" occurs when an input element's value prop transitions from being undefined (uncontrolled) to a defined value (controlled) or vice versa. To avoid this, always initialize state variables to an appropriate default value (such as an empty string for text inputs) and ensure the value prop is always defined. This will help maintain consistent behavior and prevent React from throwing warnings

HAPPY CODING AND DEBUGGING

. .