Osman Corluk
Osman Corluk

Reputation: 365

(ReactJS) "Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined ..."

The UserForm which is controlled by another controller complains on console as

"Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components input span li ul..."**

This is the component code:

export default({user}) => {
  if(!user) {
    return <div></div>
  } else {
    const [name,setName] = useState(user.name|| "")
    useEffect(() => {
      setName(user.name);
    }, [user])
              
    return <form>
             <fieldset> 
               <legend> Edit  {user.name }</legend>
               <p> 
                 <label htmlFor={"name"}> Name  </label>
                 <input  id="name" value={name || ""} onChange={e => setName(e.target.value)} />
               </p> 
             </fieldset>
           </form>
  }
}

I tried all fixes suggested on other stackoverflow answers but still gives warning.
Is there something I migth missing?

Upvotes: 7

Views: 37894

Answers (4)

moeabdol
moeabdol

Reputation: 5049

If you have your initial value undefined You can use the or notation to fallback to an initial empty string.

const [someValue, setValue] = useState<string | undefined>();
...
<input
  value={someValue ?? ''}
  onChange={e => setValue(e.target.value)}
/>

Upvotes: 0

Shubham Sarda
Shubham Sarda

Reputation: 635

This problem mainly occurs when you have an undefined value for an attribute, now it can be checked for <input /> field or type or even value attribute.

Solution: Add || with default value.

<input checked={state.bestSellerOnly || false} id="best-seller" type="checkbox" value="" className="" />

Here state.bestSellerOnly might return undefined at the time of mounting that can cause a warning for react.

Upvotes: 4

Sabaoon Bedar
Sabaoon Bedar

Reputation: 3689

Actually in react when you are facing this, it is an error of controlled and uncontrolled components, in Simple words: controlled components: are the components which uses states to change their values. Example:

<input
onChange={(e)=>{whatever!}
value={from your state}
/>

while in uncontrolled components states are not involved that much you can simply use defaultValue or ref. Example:

<input
defaultValue={from your state or from anywhere}
/>

Remember: if you are using both value and defaultValue it will give you the error because we can't use value of controlled component and defaultValue of uncontrolled component together.

Hint: you need to choose between controlled and uncontrolled components, so using ref would be solution for it. or Changing value to defaultValue will resolve it.

Note:

defaultValue is only for the initial load. If you want to initialise the input then you should use defaultValue, but if you want to use state to change the value then you need to use value. Read this for more React input defaultValue doesn't update with state

I used the below way in the input to get rid of that warning,define the value property by using Short-circuit evaluation like this:

value={this.state.fields.name || ''}   // (undefined || '') = ''

Another Approach

The reason is, in state you defined:

this.state = { fields: {} }

fields as a blank object, so during the first rendering this.state.fields.name will be undefined, and the input field will get its value as:

value={undefined}

Because of that, the input field will become uncontrolled.

Once you enter any value in input, fields in state gets changed to:

this.state = { fields: {name: 'xyz'} }

And at that time the input field gets converted into a controlled component; that's why you are getting the error:

A component is changing an uncontrolled input of type text to be controlled.

Possible Solution:

1- Define the fields in state as:

this.state = { fields: {name: ''} }

Upvotes: 15

Andy
Andy

Reputation: 63524

I've updated your component with this working example.

const { useEffect, useState } = React;

function Example({ user }) {

  const [name, setName] = useState(user.name);

  function handleChange(e) {
    setName(e.target.value);
  }

  useEffect(() => console.log(name), [name]);

  if(!user) return <div></div>;

  return (
    <form>
      <fieldset> 
        <legend>Edit name</legend>
        <input
          id="name"
          value={name}
          onChange={handleChange}
        />
      </fieldset>
    </form>
  )
};

const user = { name: 'Bob' };

ReactDOM.render(
  <Example user={user} />,
  document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Upvotes: 3

Related Questions