React.js - input che perde il focus quando si esegue nuovamente il rendering


97

Sto solo scrivendo per un input di testo e nel onChangecaso chiamo setState, quindi React riproduce nuovamente la mia interfaccia utente. Il problema è che l'input di testo perde sempre il focus, quindi ho bisogno di focalizzarlo di nuovo per ogni lettera: 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>
        );
    }

});

L'unico motivo per cui accadrebbe è se a) qualcos'altro ruba la messa a fuoco, o b) l'input è tremolante. Potresti fornire un jsfiddle / jsbin che mostra il problema? Ecco una reazione di base jsbin .
Brigante

lol ... beh, suona un po 'fastidioso: P Con jquery imposterei un identificatore per il nuovo file di input renderizzato e poi chiamerei il focus su di esso. Non sono sicuro di come apparirebbe il codice in plain js. Ma sono sicuro che puoi farlo :)
Medda86

1
@FakeRainBrigand: cosa intendi per sfarfallio?
Krab

@Krab, come se this.props.selected stesse diventando falso e poi diventasse di nuovo vero. Ciò causerebbe lo smontaggio dell'input e quindi il montaggio di nuovo.
Brigante

@ Krab, prova a rimuovere le linee slimScroll; potrebbe fare qualcosa di strano che sta causando problemi.
Brigante

Risposte:


87

Senza vedere il resto del codice, questa è una supposizione. Quando crei un EditorContainer, specifica una chiave univoca per il componente:

<EditorContainer key="editor1"/>

Quando si verifica un nuovo rendering, se viene visualizzata la stessa chiave, questo dirà a React di non distruggere e rigenerare la vista, invece di riutilizzarla. Quindi l'elemento focalizzato dovrebbe mantenere il focus.


2
Questo ha risolto il mio problema con il rendering di un sottocomponente contenente un modulo di input. Ma nel mio caso avevo bisogno del contrario: il modulo non veniva riprodotto nuovamente quando volevo. L'aggiunta dell'attributo chiave ha funzionato.
Sam Texas

2
l'aggiunta di chiave all'ingresso non ha aiutato
Mïchael Makaröv

6
Forse anche i componenti che contengono il tuo input vengono nuovamente renderizzati. Controllare le chiavi sui componenti che racchiudono.
Petr Gladkikh,

Voto positivo di @PetrGladkikh. Avevo bisogno di chiavi su tutti i componenti che lo racchiudevano.
Brian Cragun

49

Continuo a tornare qui ancora e ancora e alla fine trovo sempre la soluzione al mio altrove. Quindi, lo documenterò qui perché so che lo dimenticherò di nuovo!

Il motivo per cui inputstavo perdendo la concentrazione nel mio caso era dovuto al fatto che stavo riproducendo nuovamente il inputcambiamento di stato attivo.

Codice 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>
    )
  }
}

Quindi, il problema è che inizio sempre a codificare tutto in un unico posto per testarlo rapidamente e successivamente suddividerlo in moduli separati. Ma qui questa strategia si ritorce contro perché l'aggiornamento dello stato al cambio di input innesca la funzione di rendering e il focus viene perso.

La correzione è semplice, fai la modularizzazione dall'inizio, in altre parole, "Sposta il Inputcomponente fuori renderfunzione"

Codice fisso

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>
    )
  }
}

Rif. alla soluzione: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947


1
Stavo usando un HOC all'interno della funzione di rendering, spostare la chiamata HOC alla definizione del componente ha funzionato. In qualche modo simile a questa risposta.
Mark E

3
Penso che questo sia il mio problema, ma ho problemi a spostare i componenti all'esterno render()e a class ... extends Componentcausa del riferimento a this. ad esempioonChange={this.handleInputChange}
Nateous

3
Il morale della storia, per i componenti della classe React, non definisce i componenti all'interno della renderfunzione, per i componenti funzionali React, non definisce i componenti nel corpo della funzione.
Wong Jia Hau,

1
Grazie! Mi hai salvato il culo !!
K-Sato

1
@ Nateous ho avuto esattamente la stessa situazione. HOC chiama all'interno della renderfunzione a cui è stato passato il metodo "this.handleChange" e restituisce i componenti da utilizzare. Ad esempio const TextAreaDataField = TextAreaDataFieldFactory(this.handleChange). Ho semplicemente spostato la creazione del componente nel costruttore e accedervi nel rendermetodo come this.TextAreaDataField. Ha funzionato come un fascino.
Marcus Junius Brutus il

36

Se è un problema all'interno di un router React <Route/>usa il renderprop invece di component.

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


La perdita di concentrazione si verifica perché l' componentelica utilizza React.createElementogni volta invece di riprodurre nuovamente le modifiche.

Dettagli qui: https://reacttraining.com/react-router/web/api/Route/component


2
Questo era essenzialmente il mio problema. In particolare, stavo passando una funzione anonima al componente prop, innescando la perdita di focus:, <Route component={() => <MyComponent/>} />quando avrei dovuto farlo<Route component={MyComponent} />
Daniel

Mi hai salvato la giornata - semplice come sembra!
Fabricio

12

La mia risposta è simile a ciò che ha detto @ z5h.

Nel mio caso, Math.random()generavo un unico keyper il componente.

Ho pensato che fosse keyusato solo per attivare un rerender per quel particolare componente piuttosto che ri-renderizzare tutti i componenti in quell'array (restituisco un array di componenti nel mio codice). Non sapevo che fosse usato per ripristinare lo stato dopo aver eseguito nuovamente il rendering.

Rimuoverlo ha fatto il lavoro per me.


1
Un modulo come npmjs.com/package/shortid è anche un bel modo per gestire qualcosa di simile.
skwidbreth

4

L'applicazione autoFocusdell'attributo inputall'elemento può fungere da soluzione alternativa in situazioni in cui è presente un solo input che deve essere focalizzato. In tal caso un keyattributo non sarebbe necessario perché è solo un elemento e inoltre non dovresti preoccuparti di scomporre l' inputelemento nel suo componente per evitare di perdere il focus sul ri-rendering del componente principale.


4

Ho lo stesso comportamento.

Il problema nel mio codice era che ho creato un array nidificato di elementi jsx come questo:

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

...

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

Ogni elemento in questo Array annidato viene rieseguito ogni volta che ho aggiornato l'elemento padre. E così gli input perdono ogni volta il "ref" prop

Ho risolto il problema con la trasformazione dell'array interno in un componente di reazione (una funzione con una funzione di rendering)

const example = [
            <myComponentArray />
        ]

 ...

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

MODIFICARE:

Lo stesso problema si verifica quando creo un file annidato React.Fragment

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

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

3

Mi sono appena imbattuto in questo problema e sono venuto qui per chiedere aiuto. Controlla il tuo CSS! Il campo di input non può avere user-select: none;o non funzionerà su un iPad.


3

Ho risolto lo stesso problema eliminando l'attributo chiave nell'input e i suoi elementi principali

// 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)
    }}
/>


2

Per me, questo era causato dal rendering della casella di input di ricerca nello stesso componente (chiamato UserList) dell'elenco dei risultati della ricerca. Pertanto, ogni volta che i risultati della ricerca cambiano, viene eseguito nuovamente il rendering dell'intero componente UserList, inclusa la casella di input.

La mia soluzione era creare un componente completamente nuovo chiamato UserListSearch che è separato da UserList . Non avevo bisogno di impostare le chiavi sui campi di input in UserListSearch perché funzionasse. La funzione di rendering del mio UsersContainer ora ha questo aspetto:

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>  
    )
  }
}

Spero che questo aiuti anche qualcuno.


2

Se il campo di input si trova all'interno di un altro elemento (ad esempio, un elemento contenitore come <div key={"bart"}...><input key={"lisa"}...> ... </input></div>- i puntini di sospensione qui che indicano il codice omesso), deve esserci una chiave univoca e costante sull'elemento contenitore (così come sul campo di input) . Altrimenti, React esegue il rendering di un elemento contenitore nuovo di zecca quando lo stato del bambino viene aggiornato anziché semplicemente rieseguire il rendering del vecchio contenitore. Logicamente, dovrebbe essere aggiornato solo l'elemento figlio, ma ...

Ho avuto questo problema durante il tentativo di scrivere un componente che richiedeva molte informazioni sull'indirizzo. Il codice funzionante ha questo aspetto

// 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>
    )
  }
}

I lettori attenti noteranno che <fieldset>è un elemento contenitore, ma non richiede una chiave. Lo stesso vale per <>e <React.Fragment>o addirittura <div>perché? Forse solo il container immediato ha bisogno di una chiave. Non so. Come dicono i libri di testo di matematica, la spiegazione è lasciata al lettore come esercizio.


1

Il motivo principale è: quando React ri-renderizza, il tuo riferimento DOM precedente non sarà valido. Significa che react ha cambiato l'albero DOM e tu this.refs.input.focus non funzionerà, perché l'input qui non esiste più.


1

Le risposte fornite non mi hanno aiutato, ecco cosa ho fatto ma ho avuto una situazione unica.

Per ripulire il codice tendo a utilizzare questo formato fino a quando non sono pronto a inserire il componente in un altro file.

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

Ma questo ha causato la perdita di concentrazione, quando ho inserito il codice direttamente nel div ha funzionato.

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

Non so perché questo sia, questo è l'unico problema che ho avuto a scriverlo in questo modo e lo faccio nella maggior parte dei file che ho, ma se qualcuno fa una cosa simile è per questo che perde il focus.


1

Ho avuto lo stesso problema con una tabella html in cui ho righe di testo di input in una colonna. all'interno di un loop leggo un oggetto json e creo righe in particolare ho una colonna con inputtext.

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

Sono riuscito a risolverlo nel modo seguente

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>
            );
          }
        }

0

È venuto fuori che mi stavo thiscollegando al componente che ne causava il rendering.

Ho pensato di pubblicarlo qui nel caso qualcun altro avesse questo problema.

Ho dovuto cambiare

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

Per

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

Semplice correzione poiché nel mio caso, non ho davvero bisogno thisdi renderField, ma spero mi distacco questo aiuterà qualcun altro.


0

Sono passato valuea defaultValue. Per me va bene.

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

// after
<input defaultValue={myVar} />

0

Il mio problema era che ho chiamato la mia chiave dinamicamente con un valore dell'elemento, nel mio caso "nome", quindi la chiave era key = { ${item.name}-${index}}. Quindi, quando volevo cambiare l'input con item.name come valore, anche la chiave cambierebbe e quindi reagire non riconoscerebbe quell'elemento


0

Ho avuto questo problema e il problema è risultato che stavo usando un componente funzionale e collegandomi con lo stato di un componente genitore. Se sono passato a utilizzare un componente di classe, il problema è andato via. Si spera che ci sia un modo per aggirare questo quando si usano componenti funzionali poiché è molto più conveniente per semplici renderizzatori di oggetti e altri.


0

incluso il codice successivo nell'input del tag:

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

Prima:

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

Dopo:

<input
     ref={(input) => {
          if (input) {
             input.focus();
          }
      }}
      defaultValue={email}
      className="form-control"
      type="email"
      id="email"
      name="email"
      placeholder={"mail@mail.com"}
      maxLength="15"
      onChange={(e) => validEmail(e.target.value)}
/>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.