Material-ui: [TextField] perde o foco no reenvio

Criado em 8 jun. 2015  ·  41Comentários  ·  Fonte: mui-org/material-ui

Se um campo de texto que tinha foco é renderizado novamente por setState , ele perde o foco.

question v0.x

Comentários muito úteis

@scotmatson Posso confirmar que, pelo menos no meu caso, o problema será resolvido se eu transformar o Container = styled.div que tenho como invólucro em um componente normal sem estilo div .

Editar:
E também compartilho suas frustrações. Seria muito bom se essas duas bibliotecas funcionassem juntas na perfeição. Esta questão e a questão da especificidade são duas coisas que espero que sejam resolvidas em breve, mas sou grato pelas pessoas que dedicaram seu tempo.

Edit2:
Acabei de descobrir o que estava causando o problema no meu caso: declarar componentes de componentes estilizados no método de renderização! Isso estava fazendo com que os componentes fossem recriados em cada renderização novamente e o React não conseguia rastrear qual elemento estava em foco no limite da renderização. Isso também estava levando a alguns outros problemas de renderização para mim.

Mover todas as minhas declarações de componentes estilizados para fora do meu componente resolveu os problemas de perda de foco para mim. Provavelmente um erro noob, mas ninguém mencionou isso aqui, então pensei em voltar e relatar.

Todos 41 comentários

+1

Tenho que usar o evento onBlur com defaultValue para fazer os Textfields funcionarem perfeitamente.

Alguém tem uma solução alternativa para fazer isso funcionar?

Não acho mais isso um problema. Veja o código abaixo:

//taken from examples/webpack-example

/** In this file, we create a React component which incorporates components provided by material-ui */

const React = require('react');
const RaisedButton = require('material-ui/lib/raised-button');
const TextField = require('material-ui/lib/text-field');
const Dialog = require('material-ui/lib/dialog');
const ThemeManager = require('material-ui/lib/styles/theme-manager');
const LightRawTheme = require('material-ui/lib/styles/raw-themes/light-raw-theme');
const Colors = require('material-ui/lib/styles/colors');

const Main = React.createClass({

  childContextTypes: {
    muiTheme: React.PropTypes.object,
  },

  getInitialState () {
    return {
      muiTheme: ThemeManager.getMuiTheme(LightRawTheme),
      counter: 0,
    };
  },

  getChildContext() {
    return {
      muiTheme: this.state.muiTheme,
    };
  },

  componentWillMount() {
    let newMuiTheme = ThemeManager.modifyRawThemePalette(this.state.muiTheme, {
      accent1Color: Colors.deepOrange500,
    });

    this.setState({muiTheme: newMuiTheme});
  },

  componentDidMount() {
    this.refs.textField.focus();
  },

  render() {

    console.log(this.state.counter);

    let containerStyle = {
      textAlign: 'center',
      paddingTop: '200px',
    };

    let standardActions = [
      { text: 'Okay' },
    ];

    return (
      <div style={containerStyle}>
        <Dialog
          title="Super Secret Password"
          actions={standardActions}
          ref="superSecretPasswordDialog">
          1-2-3-4-5
        </Dialog>

        <h1>material-ui</h1>
        <h2>example project</h2>

        <TextField ref="textField" onChange = {this._incrementStateCounter}/>
        <RaisedButton label="Super Secret Password" primary={true} onTouchTap={this._handleTouchTap} />

      </div>
    );
  },

  _handleTouchTap() {
    this.refs.superSecretPasswordDialog.show();
  },

  _incrementStateCounter () {
    this.setState({counter: this.state.counter + 1});
  },

});

module.exports = Main;


Aqui está o aplicativo em ação (com o estado atualizado sendo registrado no console conforme a entrada TextField muda). Você pode ver que o TextField mantém o foco:

4uo63bzhmj

Eu ainda entendo isso. O exemplo
para reproduzir usar
<TextField ref="textField" value={this.state.valueToBeEdited} onChange = {this._incrementStateCounter}/>

ou

<TextField ref="textField" defaultValue={this.state.valueToBeEdited} onChange = {this._incrementStateCounter}/>

Também estou vendo o que parece ser uma regressão desse problema. Meus TextFields estão perdendo o foco na re-renderização. Estou configurando o value prop de TextField , portanto, é um componente controlado.

Noto um novo aviso no console: 'TextField está alterando uma entrada não controlada do tipo de texto a ser controlado'. Não me lembro de ter visto isso antes, não tenho certeza se é relevante aqui ou não. O aviso aparece quando eu insiro o primeiro caractere em TextField .

Por meio de uma pequena depuração, parece que algo está fazendo com que TextField perca o foco. A interna onBlur manipulador é invocado, que alterna o isFocused estado interno do TextField para falso. Se eu logar document.activeElement durante o manipulador onBlur (para determinar qual componente roubou o foco), ele registra a raiz do documento (corpo).

Consegui diminuir ainda mais determinando que a origem do evento de desfoque foi o primeiro MenuItem em Menu em outro lugar no aplicativo. Definir a propriedade disableAutoFocus como true resolveu meu problema. Espero que isso ajude mais alguém.

Abri uma edição separada para capturar isso: # 4387.

Posso confirmar que isso ainda é um problema ...

Vendo isso em um formulário de login muito simples - estou usando isso em conjunto com os formulários redux e quando interajo com o formulário, ele causa uma re-renderização (tocou um campo na linguagem redux-form) que perde o foco.

Como solução alternativa, só estou renderizando novamente o formulário real se houver uma alteração no estado de erro.

Você pode reproduzir esse problema facilmente colocando TextField em Table definido como selectable=true . Sempre que você clicar em um campo de texto para começar a editá-lo, a linha receberá o foco, mudando sua cor de fundo (acho que alguns props da linha podem ser definidos como selected=true ), acione uma nova renderização, assim você perderá o foco ...

Então, você basicamente não pode usar TextField dentro de selectable Table , não tenho certeza se é uma boa prática de UX de qualquer maneira, mas ainda assim :)

Percebemos que em nosso projeto, posso tentar empurrar um reprodutor.

Qualquer solução para isso? Observe isso também ao usar uma área de texto de entrada normal dentro de uma tabela.

@ dalexander01 Encontrou uma "solução", mas parece muito específica do redux-form, pois estava tendo o mesmo problema ao não usar componentes de entrada MD. Se for útil, posso postar um snippet!

@deyceg por favor, não faz mal 😄. Basta encontrar esse problema com esta combinação de <Dialog/> e <TextField/> .

      <Dialog
        open={this.props.showDialog}
        title={'New ' + this.props.type}
        autoScrollBodyContent={true}
        actions={actions}
        bodyStyle={styles.dialogContent}
      >
        <SelectField
          name='sub_type'
          value={this.props.top_card.sub_type}
          onChange={(e, k, payload) => this.props.topCardSelectChange('sub_type', payload)}
          floatingLabelText='Type'
        >
          {menuItemsJSX}
        </SelectField>
        <TextField
          name='title'
          className='story-title'
          value={this.props.top_card.title}
          onChange={this.props.topCardChange}
          floatingLabelText='Title'
          fullWidth={true}
          multiLine={true}
          style={styles.title}
        />
        <TextField />
      </Dialog>

Eu resolvi para o meu caso, você precisa fornecer um id exclusivo para esse campo de texto.
...
O id não deve mudar após a renderização. Dessa forma, o React pode rastrear qual elemento foi focalizado ou não.
PS Eu tinha textfields em um array que é renderizado via myArray.map, lá você tem que fornecer as mesmas chaves ao renderizar.

EDIT: Eu testei novamente, apenas ter a "chave" em myArray para ser a mesma em todos os renderizadores resolveu o problema.
O id do campo de texto mudou para shortid.generate ().

    params.map(function(p,i){
        return(
           <div key={i}>              <--- this will always be the same for this particular row.
               <div className='param-inner'>
                   <TextField id={shortid.generate()} value = {p.value} onChange={this.updateParam.bind(this,i)}/>               <-- id here can be random, but it is necessary to avoid browser warning "we don't have enough information..."
                   </div>
               <div className='param-inner'>{p.unit}</div>
           </div>
        )
    }.bind(this));

Alguém conseguiu encontrar uma solução alternativa para esse problema (sem usar refs )? Parece ser a combinação de onChange e definir o value prop para uma chave de estado que faz com que a entrada perca o foco.

Eu também tive o seguinte problema. Resolvi-o tendo a implementação de SelectField se você vê-lo internamente chamando um DropdownMenu https://github.com/callemall/material-ui/blob/ccf712c5733508784cd709c18c29059542d6aad1/src/SelectField/SelectField. js # L192

então eu também preciso adicionar um DropDownMenu que internamente envolve os componentes em um Menu, adicionei disableAutoFocus para fazer meu TextField manter o foco nele como mostrado abaixo.

Talvez possamos expor um componente DropDownMenu do SelectField e permitir que o foco automático seja desativado com adereços.

out

Uma solução alternativa para isso para aqueles que estão lutando com isso e apenas querem que algo funcione, embora seja possível que não seja aplicável em algumas situações e possa apresentar problemas (eu não estou totalmente certo de mim mesmo) é usar uma variável na instância, em vez de do que o estado. Então, em vez de this.state.value seria this.value que onChange estaria atualizando. O componente não teria um prop value . Então, no manipulador onChange você usaria e.target.value .

A única diferença que conheço entre uma variável na instância e o estado é que a instância var não acionará uma nova renderização. Embora possa haver outros motivos, então estou esperando que os outros me ajudem neste.

Ainda estou recebendo este erro. Qualquer solução alternativa?

A solução alternativa que encontrei foi substituir a função shouldComponentUpdate para meu componente e, em seguida, retornar false quando alterei os valores de estado usados ​​por meus campos de texto.

`shouldComponentUpdate(nextProps, nextState) {    

    if(nextState.email != this.state.email) {  

      return false;  

    }

    else if(nextState.password != this.state.password) {  

      return false;  

    }  
    else return true;  
    }

`

Fazer assim resolveu meu problema. O problema parece ser a renderização do componente toda vez que o estado muda, o que redefine o foco. Portanto, usando a função acima para desabilitar a renderização quando você faz um evento onChange para definir valores de estado, o componente não recarrega.

@ Aspintyl000 isso funciona ... um pouco. Quando estou configurando o valor do campo de texto por meio da prop 'valor' e estou configurando essa prop para 'this.state.value', nada aparece. Embora, quando eu removo isso, pareça funcionar.

Estou usando textfields em uma linha de tabela, e isso é um problema :( algum progresso?

Psid.txt

Eu também estava recebendo o mesmo erro. Mas depois de usar componentDidMount () {
this.refs.textField.focus ();
}
não perde o foco. Mas não mostra o valor de entrada.

Tentei adicionar <TextField /> em um <Table /> . Como nas situações acima, não consigo focar a entrada.
Minha solução: adicionar um evento onClick ao , quando eu clicar nele, definirá os selectable=false da mesa. Após sua operação de entrada, use um evento onBlue para definir o selectable=true da mesa.

Comportamento bastante desagradável.

Eu tinha experimentado isso depois de envolver duas caixas de entrada com um componente estilizado. O div externo quebrou completamente o comportamento. Esta é uma falha terrível no design desta biblioteca.

Portanto, esta é uma solução bastante terrível, mas o seguinte funciona, principalmente:

`` ``
onSomeValueChanged = event => {
this.props.someValueChanged (event.target.value);

const { target } = event;
setTimeout(() => {
  target.focus();
}, 10);

};
`` ``

Sim, não é bonito ...

@scotmatson " styled-components .

@scotmatson Posso confirmar que, pelo menos no meu caso, o problema será resolvido se eu transformar o Container = styled.div que tenho como invólucro em um componente normal sem estilo div .

Editar:
E também compartilho suas frustrações. Seria muito bom se essas duas bibliotecas funcionassem juntas na perfeição. Esta questão e a questão da especificidade são duas coisas que espero que sejam resolvidas em breve, mas sou grato pelas pessoas que dedicaram seu tempo.

Edit2:
Acabei de descobrir o que estava causando o problema no meu caso: declarar componentes de componentes estilizados no método de renderização! Isso estava fazendo com que os componentes fossem recriados em cada renderização novamente e o React não conseguia rastrear qual elemento estava em foco no limite da renderização. Isso também estava levando a alguns outros problemas de renderização para mim.

Mover todas as minhas declarações de componentes estilizados para fora do meu componente resolveu os problemas de perda de foco para mim. Provavelmente um erro noob, mas ninguém mencionou isso aqui, então pensei em voltar e relatar.

O problema que parece que estou tendo é que o foco é muito sempre que um TextFields (ou Input) está sendo renderizado dentro de um object.map (). Os TextFields fora dos mapas estão funcionando perfeitamente. Estou perdendo algum adereço ou qualquer outra coisa? Estou totalmente perdida

Tentei quase todas as soluções sugeridas neste tópico sem qualquer sorte.

A estrutura bruta é esta

Arquivo Principal

...
    function Steps(props) {
      const { steps } = props;
      if(steps) {
        return steps.map((step, index) => (
          <div key={(step.order === '' ? index : step.order)}>
            <NewUxCard step={step} index={index} onChange={props.onChange} 
              onChangeCard={props.onChangeCard} onParamChange={props.onParamChange}/>
          </div>
        ));
      }
    }  

<div>
  <TextField/> //working
  <Steps steps={this.props.ux.steps} onChange={this.onChange} 
              onChangeCard={this.handleChangeCard} onParamChange={this.handleParamChange} /> 
</div>

NewUxCard

...
<div>
  <TextField type="text" fullWidth id={"qq"+this.props.index} label="Name" 
                    value={this.props.step.name} onChange={this.props.onChangeCard('name', this.props.index)} margin="normal" /> //not working - loses focus on each key stroke
</div>

Vale a pena notar que estou usando Redux-React, então as mudanças de estado passam por entrada-> ação-> redutor

Alguma ideia? obrigado

EDIT 1:
Uma investigação mais aprofundada do problema, reduzi-o e resolvi removendo a função de reação Steps (props) que é usada para criar uma tag(nesse caso).

Antes, a estrutura era-> etapas da função (adereços) -> Classe de mapeamento. Então, agora, removi a função (o intermediário) e apenas Const Steps -> Mapping Class. Só posso especular por que isso está funcionando (talvez alguns adereços tenham sido ignorados), mas resolveu meu caso.

@piltsen Acabei de encontrar esse problema na v1.5.0 ao renderizar TextFields em um .map() - reduzi-o para que minha chave TextField tivesse o mesmo valor da entrada, então, quando mudou, provavelmente causou a montagem do TextField novamente e perder o foco. Não tenho certeza se isso ajuda você - embora eu tenha certeza que agora você já resolveu

Este também é um sintoma do uso de acessórios de renderização. Fazendo algo como

<HigherOrderComponent
    render={hocProps => <TextField value={...} onChange={...} />
/>

pode causar esse comportamento. Para corrigir, passe um fragmento, se permitido pelo design:

<HigherOrderComponentThatDoesntPassOwnState
    component={<TextField  {...this.props}/>}
/>

No meu caso, parece que o foco não foi perdido, mas o cursor desapareceu do TextField. Posso verificar se document.activeElement é igual ao meu TextField, posso usar o comando do teclado e a roda do mouse para alterar o valor (digite o número), mas cada renderização do redutor de tipo acontece e o cursor perde.

Então, no topo da renderização, procuro por id meu TextField e faço

MyTextField.blur();
MyTextField.focus();

, o que não está ajudando. Mas quando faço focus () com algum timeout , parece estar funcionando.

Editar:
Bem, dentro do HOC ele ainda não está funcionando, então parece que é necessário aumentá-lo de qualquer maneira.

@ younes0 Posso confirmar que isso também corrige para mim. No meu caso, eu tinha um componente sem estado auxiliar que retornaria um TextField com alguns adereços comuns definidos e, em seguida, espalharia o resto dos adereços nele, então não tive que especificar coisas como className ou variant várias vezes em todo o formulário. Meus campos de texto estavam perdendo o foco assim que qualquer alteração era feita no formulário e removendo a função auxiliar e apenas fazendo TextField s brutos repetidamente consertando isso.

Isso pode ser reaberto ou um novo fascículo deve ser arquivado?

Ainda estou tendo esse problema, estou usando o 3.2.0

@OmarDavilaP Você pode criar um novo exemplar com uma reprodução?

Eu resolvi para o meu caso, você precisa fornecer um id exclusivo para esse campo de texto.
...
O id não deve mudar após a renderização. Dessa forma, o React pode rastrear qual elemento foi focalizado ou não.
PS Eu tinha textfields em um array que é renderizado via myArray.map, lá você tem que fornecer as mesmas chaves ao renderizar.

EDIT: Eu testei novamente, apenas ter a "chave" em myArray para ser a mesma em todos os renderizadores resolveu o problema.
O id do campo de texto mudou para shortid.generate ().

    params.map(function(p,i){
        return(
           <div key={i}>              <--- this will always be the same for this particular row.
               <div className='param-inner'>
                   <TextField id={shortid.generate()} value = {p.value} onChange={this.updateParam.bind(this,i)}/>               <-- id here can be random, but it is necessary to avoid browser warning "we don't have enough information..."
                   </div>
               <div className='param-inner'>{p.unit}</div>
           </div>
        )
    }.bind(this));

Sim, isso resolve o problema no meu caso, basicamente, eu estava usando:

<NewTextField key={user.id+Math.random()}>
Eu substituí por:

<NewTextField key={user.id}>
Então, sim, parece uma prática ruim gerar chaves aleatórias cada vez que ele renderizar novamente o componente.

Para mim, o problema era, eu criei uma função de wrapper para TextField, no entanto, declarei essa função dentro da minha função de componente, uma vez movido para fora da minha função, tudo funcionou como esperado!

Criei um exemplo de caixa de envio https://codesandbox.io/s/material-demo-msbxl?fontsize=14&hidenavigation=1&theme=dark

Estou tendo esse problema com a renderização condicional. Eu tenho um objeto javascript na renderização que retorna vários componentes no arquivo .js. Não estou descrevendo corretamente, desculpe. Quando eu consertar, vou editar este post, se me lembrar.

editar: Yay, consertei! Acredito que a maneira como eu estava renderizando condicionalmente estava renderizando cada componente mesmo quando não era exibido. Mudei a lógica para fora do render e parece funcionar bem agora. Aqui está o código antigo:

editar: não consigo estilizar essa coisa, então deixa pra lá.

Eu encontrei esse problema hoje e bati minha cabeça nele por algumas horas. No meu caso, o que eu estava fazendo de errado foi envolver TextField em um componente sem estado para organizar melhor minha página (não renderizar se os dados estiverem carregando, etc etc), mas deixei useState callback que controlava TextField no componente pai.

Para a posteridade, aqui está um pequeno link codesandbox para reproduzir minha estupidez: https://codesandbox.io/s/mui-textedit-losefocus-g6xxb

A questão agora é: como eu criaria meu envoltório de componente personalizado TextField ? Ambos HOC e createElement parecem incorrer neste bug:

  const MyField= ({ name, ...props }) => (
    <TextField
      fullWidth
      variant="outlined"
      size="small"
      name={name}
      {...props}
    />
  );
  const MyField= ({ name, ...rest }) => {
    return React.createElement(TextField, {
      fullWidth: true,
      variant: "outlined",
      size: "small",
      name: name,
      {...rest}
    });
  };

Esqueça, a abordagem acima funciona bem - percebi que havia definido o componente personalizado dentro do componente pai, o que causou o problema. Quando movido para fora do escopo pai (como deveria ser), funciona bem - atualizei a sandbox anterior para mostrar a abordagem correta: https://codesandbox.io/s/mui-textedit-losefocus-g6xxb

Para mim, o problema era, eu criei uma função de wrapper para TextField, no entanto, declarei essa função dentro da minha função de componente, uma vez movido para fora da minha função, tudo funcionou como esperado!

Criei um exemplo de caixa de envio https://codesandbox.io/s/material-demo-msbxl?fontsize=14&hidenavigation=1&theme=dark

Esse era o meu problema. Obrigado!

Esqueça, a abordagem acima funciona bem - percebi que havia definido o componente personalizado dentro do componente pai, o que causou o problema. Quando movido para fora do escopo pai (como deveria ser), funciona bem - atualizei a sandbox anterior para mostrar a abordagem correta: https://codesandbox.io/s/mui-textedit-losefocus-g6xxb

Obrigado. Isso funcionou!

Para mim, o problema era, eu criei uma função de wrapper para TextField, no entanto, declarei essa função dentro da minha função de componente, uma vez movido para fora da minha função, tudo funcionou como esperado!

Criei um exemplo de caixa de envio https://codesandbox.io/s/material-demo-msbxl?fontsize=14&hidenavigation=1&theme=dark

@ fleischman718 obrigado por descobrir isso! Remover a função do componente de agrupamento para um TextField dentro da definição de um componente pai corrigiu meu problema!

Levei um segundo para descobrir o que estava acontecendo na caixa de areia, então coloquei um resumo abaixo:

import { TextField } from '@material-ui/core';

const ParentComponent = () => {

  // DON'T DO THIS!!!
  // fix issue by moving this component definition outside of the ParentComponent
  // or assign the TextField to a variable instead
  const TextFieldWrapper = () => (
    <TextField />
  );

  return (
    <div>
      <TextFieldWrapper />
    </div>
  )
}
Esta página foi útil?
0 / 5 - 0 avaliações