React.js - perda de foco de entrada ao renderizar novamente

97

Estou apenas escrevendo para entrada de texto e, onChangecaso eu chame setState, o React processa novamente minha IU. O problema é que a entrada de texto sempre perde o foco, então preciso focalizá-la novamente para cada letra: D.

var EditorContainer = React.createClass({

    componentDidMount: function () {
        $(this.getDOMNode()).slimScroll({height: this.props.height, distance: '4px', size: '8px'});
    },

    componentDidUpdate: function () {
        console.log("zde");
        $(this.getDOMNode()).slimScroll({destroy: true}).slimScroll({height: 'auto', distance: '4px', size: '8px'});
    },

    changeSelectedComponentName: function (e) {
        //this.props.editor.selectedComponent.name = $(e.target).val();
        this.props.editor.forceUpdate();
    },

    render: function () {

        var style = {
            height: this.props.height + 'px'
        };
        return (
            <div className="container" style={style}>
                <div className="row">
                    <div className="col-xs-6">
                    {this.props.selected ? <h3>{this.props.selected.name}</h3> : ''}
                    {this.props.selected ? <input type="text" value={this.props.selected.name} onChange={this.changeSelectedComponentName} /> : ''}
                    </div>
                    <div className="col-xs-6">
                        <ComponentTree editor={this.props.editor} components={this.props.components}/>
                    </div>
                </div>
            </div>
        );
    }

});
Krab
fonte
A única razão para isso acontecer é se a) algo mais roubar o foco ou b) a entrada estiver piscando. Você poderia fornecer um jsfiddle / jsbin mostrando o problema? Aqui está uma reação básica jsbin .
Brigand
lol ... bem, isso parece um pouco chato: P Com jquery eu definiria um identificador para o novo arquivo de entrada renderizado e, em seguida, chamaria o foco nele. Não tenho certeza de como o código ficaria em js simples. Mas tenho certeza que você pode fazer isso :)
Medda86,
1
@FakeRainBrigand: o que você quer dizer com cintilação?
Krab
@Krab, como se this.props.selected estivesse se tornando falso e, em seguida, se tornando verdadeiro novamente. Isso faria com que a entrada fosse desmontada e montada novamente.
Brigand
@Krab, tente remover as linhas do slimScroll; que pode estar fazendo algo estranho que está causando problemas.
Brigand

Respostas:

87

Sem ver o resto do seu código, isso é uma suposição. Ao criar um EditorContainer, especifique uma chave exclusiva para o componente:

<EditorContainer key="editor1"/>

Quando ocorrer uma nova renderização, se a mesma chave for vista, isso dirá ao React para não destruir e regenerar a visualização, em vez disso, reutilize. Então, o item em foco deve manter o foco.

z5h
fonte
2
Isso resolveu meu problema com a renderização de um subcomponente contendo um formulário de entrada. Mas, no meu caso, precisava do contrário - o formulário não foi renderizado novamente quando eu queria. Adicionar o atributo-chave funcionou.
Sam Texas
2
adicionar chave à entrada não ajudou
Mïchael Makaröv
6
Talvez o (s) componente (s) que contêm sua entrada também sejam renderizados novamente. Verifique as chaves nos componentes anexos.
Petr Gladkikh,
Votar positivamente no comentário de @PetrGladkikh. Eu precisava de chaves em todos os componentes incluídos.
Brian Cragun
49

Eu sempre volto aqui e sempre encontro a solução para o meu outro lugar no final. Então, vou documentar aqui porque sei que vou esquecer isso de novo!

A razão para inputperder o foco no meu caso foi devido ao fato de que eu estava reproduzindo a inputmudança de estado on.

Código Buggy:

import React from 'react';
import styled from 'styled-components';

class SuperAwesomeComp extends React.Component {
  state = {
    email: ''
  };
  updateEmail = e => {
    e.preventDefault();
    this.setState({ email: e.target.value });
  };
  render() {
    const Container = styled.div``;
    const Input = styled.input``;
    return (
      <Container>
        <Input
          type="text"
          placeholder="Gimme your email!"
          onChange={this.updateEmail}
          value={this.state.email}
        />
      </Container>
    )
  }
}

Então, o problema é que eu sempre começo a codificar tudo em um lugar para testar rapidamente e depois divido tudo em módulos separados. Mas, aqui esta estratégia sai pela culatra porque atualizar o estado na mudança de entrada dispara a função de renderização e o foco é perdido.

Consertar é simples, fazer a modularização desde o início, ou seja, "Mova o Inputcomponente para fora de renderfunção"

Código Fixo

import React from 'react';
import styled from 'styled-components';

const Container = styled.div``;
const Input = styled.input``;

class SuperAwesomeComp extends React.Component {
  state = {
    email: ''
  };
  updateEmail = e => {
    e.preventDefault();
    this.setState({ email: e.target.value });
  };
  render() {
    return (
      <Container>
        <Input
          type="text"
          placeholder="Gimme your email!"
          onChange={this.updateEmail}
          value={this.state.email}
        />
      </Container>
    )
  }
}

Ref. para a solução: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947

Deepak
fonte
1
Eu estava usando um HOC dentro da função de renderização, mover a chamada HOC para a definição do componente funcionou. De alguma forma semelhante a esta resposta.
Mark E
3
Acho que esse é o meu problema, mas estou tendo problemas para mover os componentes para fora de render()e class ... extends Componentpor causa da referência a this. por exemploonChange={this.handleInputChange}
Nateous
3
O moral da história, para os componentes da classe React, não define os componentes dentro da renderfunção, para os componentes funcionais do React, não define os componentes no corpo da função.
Wong Jia Hau
1
Obrigado! Você salvou minha bunda !!
K-Sato
1
@Nateous: eu tive exatamente a mesma situação. Chamadas HOC dentro da renderfunção que estava sendo passada pelo método 'this.handleChange' e retornou os componentes a serem usados. Ex const TextAreaDataField = TextAreaDataFieldFactory(this.handleChange). Simplesmente movi a criação do componente para o construtor e os acessei no rendermétodo como this.TextAreaDataField. Funcionou como um encanto.
Marcus Junius Brutus
36

Se for um problema em um roteador react, <Route/>use o renderprop em vez de component.

<Route path="/user" render={() => <UserPage/>} />


A perda de foco acontece porque o componentadereço usa React.createElementcada vez em vez de apenas refazer as alterações.

Detalhes aqui: https://reacttraining.com/react-router/web/api/Route/component

spencer.sm
fonte
2
Este foi essencialmente o meu problema. Especificamente, eu estava passando uma função anônima para o componente prop, provocando perda de foco:, <Route component={() => <MyComponent/>} />quando deveria ter feito<Route component={MyComponent} />
Daniel
Você salvou meu dia - simples assim!
Fabricio
12

Minha resposta é semelhante ao que @ z5h disse.

No meu caso, costumava Math.random()gerar um exclusivo keypara o componente.

Achei que o keyé usado apenas para acionar um rerender para aquele componente específico, em vez de renderizar novamente todos os componentes dessa matriz (eu retorno uma matriz de componentes em meu código). Não sabia que era usado para restaurar o estado após a renderização.

Remover isso funcionou para mim.

Shankar Thyagarajan
fonte
1
Um módulo como npmjs.com/package/shortid também é uma boa maneira de lidar com algo assim.
skwidbreth de
4

Aplicar o autoFocusatributo ao inputelemento pode funcionar como uma solução alternativa em situações em que há apenas uma entrada que precisa ser focada. Nesse caso, um keyatributo seria desnecessário porque é apenas um elemento e, além disso, você não teria que se preocupar em dividir o inputelemento em seu próprio componente para evitar perder o foco na re-renderização do componente principal.

Spakmad
fonte
4

Eu tenho o mesmo comportamento.

O problema em meu código é que criei um Array aninhado de elementos jsx como este:

const example = [
            [
                <input value={'Test 1'}/>,
                <div>Test 2</div>,
                <div>Test 3</div>,
            ]
        ]

...

render = () => {
    return <div>{ example }</div>
}

Cada elemento neste Array aninhado é renderizado novamente sempre que eu atualizo o elemento pai. E assim as entradas perdem o prop "ref" cada vez

Corrigi o problema com a transformação da matriz interna em um componente de reação (uma função com uma função de renderização)

const example = [
            <myComponentArray />
        ]

 ...

render = () => {
    return <div>{ example }</div>
}

EDITAR:

O mesmo problema aparece quando eu construo um aninhado React.Fragment

const SomeComponent = (props) => (
    <React.Fragment>
        <label ... />
        <input ... />
    </React.Fragment>
);

const ParentComponent = (props) => (
    <React.Fragment>
        <SomeComponent ... />
        <div />
    </React.Fragment>
);
chrisheyn
fonte
3

Acabei de encontrar esse problema e vim aqui para obter ajuda. Verifique seu CSS! O campo de entrada não pode ter user-select: none;ou não funcionará em um iPad.

David Sinclair
fonte
3

Resolvi o mesmo problema excluindo o atributo-chave na entrada e seus elementos pai

// Before
<input
    className='invoice_table-input invoice_table-input-sm'
    type='number'
    key={ Math.random }
    defaultValue={pageIndex + 1}
    onChange={e => {
        const page = e.target.value ? Number(e.target.value) - 1 : 0
        gotoPage(page)
    }}
/>
// After
<input
    className='invoice_table-input invoice_table-input-sm'
    type='number'
    defaultValue={pageIndex + 1}
    onChange={e => {
        const page = e.target.value ? Number(e.target.value) - 1 : 0
        gotoPage(page)
    }}
/>

Adrián Pedreira
fonte
2

Para mim, isso estava sendo causado pela caixa de entrada de pesquisa sendo renderizada no mesmo componente (chamado UserList) que a lista de resultados da pesquisa. Portanto, sempre que os resultados da pesquisa mudavam, todo o componente UserList era renderizado novamente, incluindo a caixa de entrada.

Minha solução foi criar um novo componente chamado UserListSearch, que é separado do UserList . Não precisei definir chaves nos campos de entrada em UserListSearch para que isso funcionasse. A função de renderização do meu UsersContainer agora se parece com isto:

class UserContainer extends React.Component {
  render() {
    return (
      <div>
        <Route
          exact
          path={this.props.match.url}
          render={() => (
            <div>
              <UserListSearch
                handleSearchChange={this.handleSearchChange}
                searchTerm={this.state.searchTerm}
              />
              <UserList
                isLoading={this.state.isLoading}
                users={this.props.users}
                user={this.state.user}
                handleNewUserClick={this.handleNewUserClick}
              />
            </div>
          )}
        />
      </div>  
    )
  }
}

Espero que isso ajude alguém também.

Dom Eden
fonte
2

Se o campo de entrada estiver dentro de outro elemento (ou seja, um elemento de contêiner como <div key={"bart"}...><input key={"lisa"}...> ... </input></div>- as reticências aqui indicando código omitido), deve haver uma chave única e constante no elemento de contêiner (bem como no campo de entrada) . Caso contrário, o React renderiza um novo elemento de contêiner quando o estado do filho é atualizado, em vez de simplesmente renderizar novamente o contêiner antigo. Logicamente, apenas o elemento filho deve ser atualizado, mas ...

Tive esse problema ao tentar escrever um componente que exigia muitas informações de endereço. O código de trabalho é parecido com este

// import react, components
import React, { Component } from 'react'

// import various functions
import uuid from "uuid";

// import styles
import "../styles/signUp.css";

export default class Address extends Component {
  constructor(props) {
    super(props);
    this.state = {
      address1: "",
      address2: "",
      address1Key: uuid.v4(),
      address2Key: uuid.v4(),
      address1HolderKey: uuid.v4(),
      address2HolderKey: uuid.v4(),
      // omitting state information for additional address fields for brevity
    };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    event.preventDefault();
    this.setState({ [`${event.target.id}`]: event.target.value })
  }

  render() {
    return (
      <fieldset>
        <div className="labelAndField" key={this.state.address1HolderKey} >
          <label className="labelStyle" for="address1">{"Address"}</label>
          <input className="inputStyle"
            id="address1"
            name="address1"
            type="text"
            label="address1"
            placeholder=""
            value={this.state.address1}
            onChange={this.handleChange}
            key={this.state.address1Key} ></input >
        </div> 
        <div className="labelAndField" key={this.state.address2HolderKey} >
          <label className="labelStyle" for="address2">{"Address (Cont.)"}</label>
          <input className="inputStyle"
            id="address2"
            name="address2"
            type="text"
            label="address2"
            placeholder=""
            key={this.state.address2Key} ></input >
        </div>
        {/* omitting rest of address fields for brevity */}
      </fieldset>
    )
  }
}

Leitores atentos notarão que <fieldset>é um elemento que contém, mas não requer uma chave. O mesmo vale para <>e <React.Fragment>ou mesmo por <div>quê? Talvez apenas o contêiner imediato precise de uma chave. Não sei. Como dizem os livros de matemática, a explicação é deixada para o leitor como um exercício.

Charles Goodwin
fonte
1

O principal motivo é: Quando o React for renderizado novamente, sua referência de DOM anterior será inválida. Significa que o react mudou a árvore DOM, e você this.refs.input.focus não funcionará, porque a entrada aqui não existe mais.

Hlissnake
fonte
1

As respostas fornecidas não me ajudaram, aqui estava o que eu fiz, mas eu tinha uma situação única.

Para limpar o código, costumo usar esse formato até que esteja pronto para colocar o componente em outro arquivo.

render(){
   const MyInput = () => {
      return <input onChange={(e)=>this.setState({text: e.target.value}) />
   }
   return(
      <div>
         <MyInput />
      </div>
   )

Mas isso fez com que perdesse o foco, quando coloquei o código diretamente no div funcionou.

return(
   <div>
      <input onChange={(e)=>this.setState({text: e.target.value}) />
   </div>
)

Não sei por que, esse é o único problema que tive ao escrever dessa forma e faço isso na maioria dos arquivos que tenho, mas se alguém fizer algo semelhante, é por isso que perde o foco.

Kevin Danikowski
fonte
1

Tive o mesmo problema com uma tabela html em que inseri linhas de texto em uma coluna. dentro de um loop, leio um objeto json e crio linhas em particular, tenho uma coluna com texto de entrada.

http://reactkungfu.com/2015/09/react-js-loses-input-focus-on-typing/

Eu consegui resolver da seguinte maneira

import { InputTextComponent } from './InputTextComponent';
//import my  inputTextComponent 
...

var trElementList = (function (list, tableComponent) {

    var trList = [],
        trElement = undefined,
        trElementCreator = trElementCreator,
        employeeElement = undefined;



    // iterating through employee list and
    // creating row for each employee
    for (var x = 0; x < list.length; x++) {

        employeeElement = list[x];

        var trNomeImpatto = React.createElement('tr', null, <td rowSpan="4"><strong>{employeeElement['NomeTipologiaImpatto'].toUpperCase()}</strong></td>);
        trList.push(trNomeImpatto);

        trList.push(trElementCreator(employeeElement, 0, x));
        trList.push(trElementCreator(employeeElement, 1, x));
        trList.push(trElementCreator(employeeElement, 2, x));

    } // end of for  

    return trList; // returns row list

    function trElementCreator(obj, field, index) {
        var tdList = [],
            tdElement = undefined;

        //my input text
        var inputTextarea = <InputTextComponent
            idImpatto={obj['TipologiaImpattoId']}//index
            value={obj[columns[field].nota]}//initial value of the input I read from my json data source
            noteType={columns[field].nota}
            impattiComposite={tableComponent.state.impattiComposite}
            //updateImpactCompositeNote={tableComponent.updateImpactCompositeNote}
        />

        tdElement = React.createElement('td', { style: null }, inputTextarea);
        tdList.push(tdElement);


        var trComponent = createClass({

            render: function () {
                return React.createElement('tr', null, tdList);
            }
        });
        return React.createElement(trComponent);
    } // end of trElementCreator

});
...    
    //my tableComponent
    var tableComponent = createClass({
        // initial component states will be here
        // initialize values
        getInitialState: function () {
            return {
                impattiComposite: [],
                serviceId: window.sessionStorage.getItem('serviceId'),
                serviceName: window.sessionStorage.getItem('serviceName'),
                form_data: [],
                successCreation: null,
            };
        },

        //read a json data soure of the web api url
        componentDidMount: function () {
            this.serverRequest =
                $.ajax({
                    url: Url,
                    type: 'GET',
                    contentType: 'application/json',
                    data: JSON.stringify({ id: this.state.serviceId }),
                    cache: false,
                    success: function (response) {
                        this.setState({ impattiComposite: response.data });
                    }.bind(this),

                    error: function (xhr, resp, text) {
                        // show error to console
                        console.error('Error', xhr, resp, text)
                        alert(xhr, resp, text);
                    }
                });
        },

        render: function () {
    ...
    React.createElement('table', {style:null}, React.createElement('tbody', null,trElementList(this.state.impattiComposite, this),))
    ...
    }



            //my input text
            var inputTextarea = <InputTextComponent
                        idImpatto={obj['TipologiaImpattoId']}//index
                        value={obj[columns[field].nota]}//initial value of the input I read //from my json data source
                        noteType={columns[field].nota}
                        impattiComposite={tableComponent.state.impattiComposite}//impattiComposite  = my json data source

                    />//end my input text

                    tdElement = React.createElement('td', { style: null }, inputTextarea);
                    tdList.push(tdElement);//add a component

        //./InputTextComponent.js
        import React from 'react';

        export class InputTextComponent extends React.Component {
          constructor(props) {
            super(props);
            this.state = {
              idImpatto: props.idImpatto,
              value: props.value,
              noteType: props.noteType,
              _impattiComposite: props.impattiComposite,
            };
            this.updateNote = this.updateNote.bind(this);
          }

        //Update a inpute text with new value insert of the user

          updateNote(event) {
            this.setState({ value: event.target.value });//update a state of the local componet inputText
            var impattiComposite = this.state._impattiComposite;
            var index = this.state.idImpatto - 1;
            var impatto = impattiComposite[index];
            impatto[this.state.noteType] = event.target.value;
            this.setState({ _impattiComposite: impattiComposite });//update of the state of the father component (tableComponet)

          }

          render() {
            return (
              <input
                className="Form-input"
                type='text'
                value={this.state.value}
                onChange={this.updateNote}>
              </input>
            );
          }
        }
Fábrica de Software
fonte
0

Acontece que eu estava vinculando thisao componente que estava causando a renderização.

Achei melhor postá-lo aqui, caso alguém mais tivesse esse problema.

Eu tive que mudar

<Field
    label="Post Content"
    name="text"
    component={this.renderField.bind(this)}
/>

Para

<Field
    label="Post Content"
    name="text"
    component={this.renderField}
/>

Correção simples, já que no meu caso, eu realmente não precisa thisno renderField, mas espero que me postar isso vai ajudar alguém.

Matt D
fonte
0

Mudei o valuesuporte para defaultValue. Isso funciona para mim.

...
// before
<input value={myVar} />

// after
<input defaultValue={myVar} />
chrisheyn
fonte
0

Meu problema é que nomeei minha chave dinamicamente com um valor do item, no meu caso "nome", então a chave era chave = { ${item.name}-${index}}. Então, quando eu quisesse alterar a entrada com item.name como valor, a chave também mudaria e, portanto, reagiria não reconheceria esse elemento

Amogu
fonte
0

Eu tive esse problema e o problema acabou sendo que eu estava usando um componente funcional e vinculando com o estado de um componente pai. Se eu mudei para usar um componente de classe, o problema foi embora. Esperançosamente, há uma maneira de contornar isso ao usar componentes funcionais, pois é muito mais conveniente para representantes de itens simples e outros.

Kyle Crossman
fonte
0

incluiu o próximo código na entrada da tag:

ref={(input) => {
     if (input) {
         input.focus();
     }
 }}

Antes:

<input
      defaultValue={email}
      className="form-control"
      type="email"
      id="email"
      name="email"
      placeholder={"[email protected]"}
      maxLength="15"
      onChange={(e) => validEmail(e.target.value)}
/>

Depois de:

<input
     ref={(input) => {
          if (input) {
             input.focus();
          }
      }}
      defaultValue={email}
      className="form-control"
      type="email"
      id="email"
      name="email"
      placeholder={"[email protected]"}
      maxLength="15"
      onChange={(e) => validEmail(e.target.value)}
/>
Thiago Pires
fonte