React - alterando uma entrada não controlada

360

Eu tenho um componente de reação simples com o formulário que acredito ter uma entrada controlada:

import React from 'react';

export default class MyForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {}
    }

    render() {
        return (
            <form className="add-support-staff-form">
                <input name="name" type="text" value={this.state.name} onChange={this.onFieldChange('name').bind(this)}/>
            </form>
        )
    }

    onFieldChange(fieldName) {
        return function (event) {
            this.setState({[fieldName]: event.target.value});
        }
    }
}

export default MyForm;

Quando executo meu aplicativo, recebo o seguinte aviso:

Aviso: O MyForm está alterando uma entrada não controlada do tipo de texto a ser controlado. Os elementos de entrada não devem mudar de não controlado para controlado (ou vice-versa). Decida entre usar um elemento de entrada controlado ou não controlado durante a vida útil do componente

Acredito que minha entrada é controlada, pois tem um valor. Eu estou querendo saber o que estou fazendo de errado?

Estou usando o React 15.1.0

alexs333
fonte

Respostas:

510

Acredito que minha entrada é controlada, pois tem um valor.

Para que uma entrada seja controlada, seu valor deve corresponder ao de uma variável de estado.

Essa condição não foi atendida inicialmente no seu exemplo porque this.state.namenão foi definida inicialmente. Portanto, a entrada é inicialmente não controlada. Uma vez que o onChangemanipulador é acionado pela primeira vez, this.state.nameé definido. Nesse ponto, a condição acima é satisfeita e a entrada é considerada controlada. Essa transição de não controlado para controlado produz o erro visto acima.

Inicializando this.state.nameno construtor:

por exemplo

this.state = { name: '' };

a entrada será controlada desde o início, corrigindo o problema. Consulte Reagir componentes controlados para obter mais exemplos.

Não relacionado a esse erro, você deve ter apenas uma exportação padrão. O seu código acima tem dois.

fvgs
fonte
7
É difícil ler a resposta e seguir a idéia várias vezes, mas essa resposta é a maneira perfeita de contar histórias e fazer com que o espectador entenda ao mesmo tempo. Responda level god!
surajnew55
2
E se você tiver campos dinâmicos em um loop? por exemplo, você define o nome do campo name={'question_groups.${questionItem.id}'}?
user3574492
2
Como os campos dinâmicos funcionariam. Estou gerando o formulário dinamicamente e, em seguida, defina os nomes dos campos em um objeto dentro do estado, ainda preciso primeiro definir manualmente os nomes dos campos no estado e depois transferi-los para o meu objeto?
Joseph
13
Esta resposta está um pouco incorreta. Uma entrada é controlada se o valuesuporte tiver um valor não nulo / indefinido. O suporte não precisa corresponder a uma variável de estado (poderia ser apenas uma constante e o componente ainda seria considerado controlado). A resposta de Adam é mais correta e deve ser aceita.
precisa saber é o seguinte
2
Sim - esta é tecnicamente a resposta errada (mas útil). Uma observação sobre isso - se você estiver lidando com botões de opção (cujo 'valor' é controlado de maneira um pouco diferente), esse aviso de reação pode realmente ser acionado, mesmo que seu componente seja controlado (atualmente). github.com/facebook/react/issues/6779 e pode ser corrigido adicionando um !! para a veracidade de isChecked
mheavers
123

Quando você tornar o seu componente, this.state.namenão está definido, por isso é avaliado como undefinedou null, e você acaba passando value={undefined}ou value={null}ao seu input.

Quando cheques ReactDOM para ver se um campo é controlado, ele verifica para ver sevalue != null (note que é !=, não !==), e uma vez undefined == nullem JavaScript, ele decide que é descontrolada.

Portanto, quando onFieldChange()é chamado, this.state.nameé definido como um valor de sequência, sua entrada passa de descontrolada para controlada.

Se você faz this.state = {name: ''}no seu construtor, porque '' != null, sua entrada terá um valor o tempo todo e essa mensagem desaparecerá.

Leigh Brenecki
fonte
5
Pelo que vale, pode acontecer a mesma coisa this.props.<whatever>, que foi o problema do meu lado. Obrigado Adam!
Don
Sim! Isso também pode acontecer se você passar uma variável calculada na qual você define render()ou uma expressão na própria tag - qualquer coisa que seja avaliada undefined. Ainda bem que pude ajudar!
Leigh Brenecki
11
Obrigado por esta resposta: apesar de não ser a aceita, explica a questão muito melhor do que apenas "especificar a name".
precisa
11
Atualizei minha resposta para explicar como a entrada controlada funciona. Vale a pena notar que o que importa aqui não é que um valor undefinedseja passado inicialmente. Pelo contrário, é o fato de que this.state.namenão existe como uma variável de estado que torna a entrada descontrolada. Por exemplo, ter this.state = { name: undefined };resultaria na entrada sendo controlada. Deve-se entender que o que importa é de onde vem o valor, não qual é o valor.
fvgs
11
O @fvgs this.state = { name: undefined }ainda resultaria em uma entrada não controlada. <input value={this.state.name} />desugars para React.createElement('input', {value: this.state.name}). Como o acesso a uma propriedade inexistente de um objeto retorna undefined, ele é avaliado exatamente para a mesma chamada de função React.createElement('input', {value: undefined})- namese não está definido ou explicitamente definido como undefined, portanto, o React se comporta da mesma maneira. Você pode ver esse comportamento neste JSFiddle.
Leigh Brenecki
52

Outra abordagem pode ser definir o valor padrão dentro da sua entrada, assim:

 <input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>
JpCrow
fonte
11
<input name = "name" type = "text" defaultValue = "" onChange = {this.onFieldChange ('name'). bind (this)} /> Eu acho que isso também funcionaria
gandalf
Muito obrigado, era exatamente isso que eu estava procurando. Há alguma desvantagem ou problema em potencial ao usar esse padrão que devo ter em mente?
Marcel Otten
Isso funcionou para mim, como os campos são renderizados dinamicamente a partir da API, então não sei o nome do campo quando o componente é montado. Isso funcionou um prazer!
Jamie - Fenrir Digital Ltd
18

Eu sei que outros já responderam isso. Mas um fator muito importante aqui pode ajudar outras pessoas com problemas semelhantes:

Você deve ter um onChangemanipulador adicionado ao seu campo de entrada (por exemplo, campo de texto, caixa de seleção, rádio, etc.). Sempre lide com a atividade através do onChangemanipulador.

Exemplo:

<input ... onChange={ this.myChangeHandler} ... />

Quando você está trabalhando com a caixa de seleção, pode ser necessário lidar com seu checkedestado !!.

Exemplo:

<input type="checkbox" checked={!!this.state.someValue} onChange={.....} >

Referência: https://github.com/facebook/react/issues/6779#issuecomment-326314716

Muhammad Hannan
fonte
11
Isso funciona para mim, obrigado, sim, eu sou 100% por cento concordam com isso, estado inicial é {}, pelo que o valor verificado será indefinido e torná-lo não controlada,
Ping Woo
13

A solução simples para resolver esse problema é definir um valor vazio por padrão:

<input name='myInput' value={this.state.myInput || ''} onChange={this.handleChange} />
MUSTAPHA GHLISSI
fonte
8

Uma desvantagem em potencial ao definir o valor do campo como "" (sequência vazia) no construtor é se o campo for um campo opcional e for deixado sem edição. A menos que você faça alguma massagem antes de postar seu formulário, o campo será mantido no armazenamento de dados como uma sequência vazia em vez de NULL.

Essa alternativa evitará cadeias vazias:

constructor(props) {
    super(props);
    this.state = {
        name: null
    }
}

... 

<input name="name" type="text" value={this.state.name || ''}/>
Greg R Taylor
fonte
6

Ao usar onChange={this.onFieldChange('name').bind(this)}em sua entrada, você deve declarar a cadeia vazia do seu estado como um valor do campo de propriedade.

maneira incorreta:

this.state ={
       fields: {},
       errors: {},
       disabled : false
    }

Maneira correta:

this.state ={
       fields: {
         name:'',
         email: '',
         message: ''
       },
       errors: {},
       disabled : false
    }
KARTHIKEYAN.A
fonte
6

No meu caso, estava faltando algo realmente trivial.

<input value={state.myObject.inputValue} />

Meu estado era o seguinte quando recebi o aviso:

state = {
   myObject: undefined
}

Ao alternar meu estado para referenciar a entrada do meu valor , meu problema foi resolvido:

state = {
   myObject: {
      inputValue: ''
   }
}
Menelaos Kotsollaris
fonte
2
Obrigado por me ajudar a entender o verdadeiro problema que eu estava tendo
Rotimi-melhor
3

Se os props do seu componente foram passados ​​como um estado, coloque um valor padrão para suas tags de entrada

<input type="text" placeholder={object.property} value={object.property ? object.property : ""}>
Imperador Krauser
fonte
3

Defina um valor para a propriedade 'name' no estado inicial.

this.state={ name:''};

lakmal_sathyajith
fonte
3

Uma atualização para isso. Para ganchos de reação, useconst [name, setName] = useState(" ")

Jordan Mullen
fonte
Obrigado 4 pela atualização Jordan, esse erro é um pouco difícil de resolver #
Juan Salvador
Por que " "e não ""? Isso faz com que você perca qualquer texto de dica e, se você clicar e digitar, terá um espaço sorrateiro antes dos dados inseridos.
Joshua Wade
1

Isso geralmente acontece apenas quando você não está controlando o valor do arquivado quando o aplicativo foi iniciado e após algum evento ou função disparar ou o estado ser alterado, agora você está tentando controlar o valor no campo de entrada.

Essa transição de não ter controle sobre a entrada e depois ter controle sobre ela é o que faz com que o problema ocorra em primeiro lugar.

A melhor maneira de evitar isso é declarando algum valor para a entrada no construtor do componente. Para que o elemento de entrada tenha valor desde o início do aplicativo.

Ashish Singh
fonte
0

Para definir dinamicamente propriedades de estado para entradas de formulário e mantê-las controladas, você pode fazer algo assim:

const inputs = [
    { name: 'email', type: 'email', placeholder: "Enter your email"},
    { name: 'password', type: 'password', placeholder: "Enter your password"},
    { name: 'passwordConfirm', type: 'password', placeholder: "Confirm your password"},
]

class Form extends Component {
  constructor(props){
    super(props)
    this.state = {} // Notice no explicit state is set in the constructor
  }

  handleChange = (e) => {
    const { name, value } = e.target;

    this.setState({
      [name]: value
    }
  }

  handleSubmit = (e) => {
    // do something
  }

  render() {
     <form onSubmit={(e) => handleSubmit(e)}>
       { inputs.length ?
         inputs.map(input => {
           const { name, placeholder, type } = input;
           const value = this.state[name] || ''; // Does it exist? If so use it, if not use an empty string

           return <input key={name}  type={type} name={name} placeholder={placeholder} value={value} onChange={this.handleChange}/>
       }) :
         null
       }
       <button type="submit" onClick={(e) => e.preventDefault }>Submit</button>
     </form>    
  }
}
Marca
fonte
0

Simplesmente crie um fallback para '' se o this.state.name for nulo.

<input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>

Isso também funciona com as variáveis ​​useState.

RobKohr
fonte