React.js - Eingabe verliert beim erneuten Rendern den Fokus

97

Ich schreibe nur zur Texteingabe und onChangefalls ich setStateanrufe, rendert React meine Benutzeroberfläche erneut. Das Problem ist, dass die Texteingabe immer einen Fokus verliert, daher muss ich sie für jeden Buchstaben erneut fokussieren: 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>
        );
    }

});
Krabben
quelle
Der einzige Grund, der passieren würde, ist, wenn a) etwas anderes den Fokus stiehlt oder b) die Eingabe flackert. Könnten Sie eine jsfiddle / jsbin bereitstellen, die das Problem zeigt? Hier ist eine Basisreaktion jsbin .
Brigand
lol ... nun, das klingt etwas nervig: P Mit jquery würde ich einen Bezeichner für die neu gerenderte Eingabedatei setzen und dann den Fokus darauf aufrufen. Ich bin mir nicht sicher, wie der Code in einfachem js aussehen würde. Aber ich bin sicher, Sie können es tun :)
Medda86
1
@FakeRainBrigand: Was meinst du mit Flackern?
Krab
@Krab, als ob this.props.selected falsch und dann wieder wahr würde. Dies würde dazu führen, dass die Eingabe aufgehoben und dann erneut bereitgestellt wird.
Brigand
@Krab, versuchen Sie, die slimScroll-Linien zu entfernen. das könnte etwas Seltsames tun, das Probleme verursacht.
Brigand

Antworten:

86

Ohne den Rest Ihres Codes zu sehen, ist dies eine Vermutung. Geben Sie beim Erstellen eines EditorContainers einen eindeutigen Schlüssel für die Komponente an:

<EditorContainer key="editor1"/>

Wenn beim erneuten Rendern derselbe Schlüssel angezeigt wird, wird React angezeigt, dass die Ansicht nicht überlastet und neu generiert, sondern erneut verwendet wird. Dann sollte das fokussierte Objekt den Fokus behalten.

z5h
quelle
2
Dies löste mein Problem beim Rendern einer Unterkomponente, die ein Eingabeformular enthält. Aber in meinem Fall brauchte ich das Gegenteil - das Formular wurde nicht neu gerendert, wenn ich es wollte. Das Hinzufügen des Schlüsselattributs hat funktioniert.
Sam Texas
2
Das Hinzufügen eines Schlüssels zur Eingabe hat nicht geholfen
Mïchael Makaröv
5
Möglicherweise werden auch Komponenten, die Ihre Eingabe enthalten, neu gerendert. Überprüfen Sie die Schlüssel an den umschließenden Komponenten.
Petr Gladkikh
Upvote Kommentar von @PetrGladkikh. Ich brauchte Schlüssel für alle umschließenden Komponenten.
Brian Cragun
48

Ich komme immer wieder hierher zurück und finde am Ende immer die Lösung für mein anderswo. Also werde ich es hier dokumentieren, weil ich weiß, dass ich das wieder vergessen werde!

Der Grund inputdafür, dass ich in meinem Fall den Fokus verlor, war die Tatsache, dass ich den Statuswechsel erneut renderte input.

Buggy Code:

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

Das Problem ist also, dass ich immer anfange, alles an einem Ort zu codieren, um es schnell zu testen und später in separate Module aufzuteilen. Hier schlägt diese Strategie jedoch fehl, da das Aktualisieren des Status bei Eingabeänderungen die Renderfunktion auslöst und der Fokus verloren geht.

Fix ist einfach, machen Sie die Modularisierung von Anfang an, mit anderen Worten: "Verschieben Sie die InputKomponente aus der renderFunktion".

Fester Code

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. zur Lösung: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947

Deepak
quelle
1
Ich habe ein HOC innerhalb der Renderfunktion verwendet und den HOC-Aufruf in die Komponentendefinition verschoben. Irgendwie ähnlich zu dieser Antwort.
Mark E
3
Ich denke, das ist mein Problem, aber ich habe Probleme, die Komponenten außerhalb render()und class ... extends Componentaufgrund des Verweises auf zu verschieben this. zBonChange={this.handleInputChange}
Nateous
2
Die Moral der Geschichte definiert für Komponenten der React-Klasse keine Komponenten innerhalb der renderFunktion, für React-Funktionskomponenten keine Komponenten im Funktionskörper.
Wong Jia Hau
1
Vielen Dank! Du hast meinen Hintern gerettet !!
K-Sato
1
@Nateous Ich hatte genau die gleiche Situation. HOC ruft innerhalb der renderFunktion die Methode 'this.handleChange' auf und übergibt die zu verwendenden Komponenten. ZB const TextAreaDataField = TextAreaDataFieldFactory(this.handleChange). Ich habe die Komponentenerstellung einfach in den Konstruktor verschoben und in der renderMethode als auf sie zugegriffen this.TextAreaDataField. Lief wie am Schnürchen.
Marcus Junius Brutus
36

Wenn es sich um ein Problem innerhalb eines Reaktionsrouters handelt, <Route/>verwenden Sie renderstattdessen die Requisite component.

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


Der Fokusverlust tritt auf, weil die componentRequisite React.createElementjedes Mal verwendet wird, anstatt nur die Änderungen neu zu rendern.

Details hier: https://reacttraining.com/react-router/web/api/Route/component

spencer.sm
quelle
2
Dies war im Wesentlichen mein Problem. Insbesondere habe ich eine anonyme Funktion an die Komponenten-Requisite übergeben, die einen Fokusverlust auslöste:, <Route component={() => <MyComponent/>} />wenn ich es hätte tun sollen<Route component={MyComponent} />
Daniel,
Du hast meinen Tag gerettet - so einfach ist das!
Fabricio
12

Meine Antwort ähnelt der von @ z5h.

In meinem Fall habe ich Math.random()ein eindeutiges Element keyfür die Komponente generiert .

Ich dachte, das keywird nur zum Auslösen eines erneuten Renderns für diese bestimmte Komponente verwendet, anstatt alle Komponenten in diesem Array neu zu rendern (ich gebe ein Array von Komponenten in meinem Code zurück). Ich wusste nicht, dass es zum Wiederherstellen des Zustands nach dem erneuten Rendern verwendet wird.

Das zu entfernen hat den Job für mich erledigt.

Shankar Thyagarajan
quelle
1
Ein Modul wie npmjs.com/package/shortid ist auch eine gute Möglichkeit, mit so etwas umzugehen .
Skwidbreth
4

Das Anwenden des autoFocusAttributs auf das inputElement kann als Problemumgehung in Situationen dienen, in denen nur eine Eingabe fokussiert werden muss. In diesem Fall wäre ein keyAttribut nicht erforderlich, da es sich nur um ein Element handelt und Sie sich darüber hinaus keine Sorgen machen müssen, das inputElement in eine eigene Komponente zu zerlegen, um nicht den Fokus auf das erneute Rendern der Hauptkomponente zu verlieren.

spakmad
quelle
4

Ich habe das gleiche Verhalten.

Das Problem in meinem Code war, dass ich ein verschachteltes Array von jsx-Elementen wie folgt erstellt habe:

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

...

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

Jedes Element in diesem verschachtelten Array wird jedes Mal neu gerendert, wenn ich das übergeordnete Element aktualisiert habe. Und so verlieren die Eingänge dort jedes Mal "ref" prop

Ich habe das Problem behoben, indem das innere Array in eine Reaktionskomponente umgewandelt wurde (eine Funktion mit einer Renderfunktion).

const example = [
            <myComponentArray />
        ]

 ...

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

BEARBEITEN:

Das gleiche Problem tritt auf, wenn ich ein verschachteltes baue React.Fragment

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

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

Ich bin gerade auf dieses Problem gestoßen und bin hierher gekommen, um Hilfe zu holen. Überprüfen Sie Ihr CSS! Das Eingabefeld kann nicht haben user-select: none;oder es funktioniert nicht auf einem iPad.

David Sinclair
quelle
3

Ich habe das gleiche Problem gelöst, indem ich das Schlüsselattribut in der Eingabe und seine übergeordneten Elemente gelöscht habe

// 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
quelle
2

Für mich wurde dies dadurch verursacht, dass das Eingabefeld für die Suche in derselben Komponente (UserList) wie die Liste der Suchergebnisse gerendert wurde . Wenn sich also die Suchergebnisse änderten, wurde die gesamte UserList-Komponente einschließlich des Eingabefelds erneut gerendert.

Meine Lösung bestand darin, eine ganz neue Komponente namens UserListSearch zu erstellen, die von UserList getrennt ist . Ich musste keine Schlüssel für die Eingabefelder in UserListSearch festlegen, damit dies funktioniert. Die Renderfunktion meines UsersContainer sieht nun folgendermaßen aus:

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

Hoffentlich hilft das auch jemandem.

Dom Eden
quelle
2

Befindet sich das Eingabefeld in einem anderen Element (dh einem Containerelement wie <div key={"bart"}...><input key={"lisa"}...> ... </input></div>- die Ellipsen hier zeigen ausgelassenen Code an), muss auf dem Containerelement (sowie auf dem Eingabefeld) ein eindeutiger und konstanter Schlüssel vorhanden sein . Andernfalls rendert React ein brandneues Containerelement, wenn der Status des Kindes aktualisiert wird, anstatt nur den alten Container neu zu rendern. Logischerweise sollte nur das untergeordnete Element aktualisiert werden, aber ...

Ich hatte dieses Problem beim Versuch, eine Komponente zu schreiben, die eine Reihe von Adressinformationen enthielt. Der Arbeitscode sieht so aus

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

Scharfäugige Leser werden feststellen, dass dies <fieldset>ein enthaltendes Element ist, für das jedoch kein Schlüssel erforderlich ist. Das gleiche gilt für <>und <React.Fragment>oder sogar <div>warum? Vielleicht braucht nur der unmittelbare Container einen Schlüssel. Ich weiß nicht. Wie Mathematiklehrbücher sagen, bleibt die Erklärung dem Leser als Übung überlassen.

Charles Goodwin
quelle
1

Der Hauptgrund ist: Wenn React erneut gerendert wird, ist Ihre vorherige DOM-Referenz ungültig. Dies bedeutet, dass durch Reagieren der DOM-Baum geändert wurde und Sie this.refs.input.focus nicht funktionieren, da die Eingabe hier nicht mehr vorhanden ist.

hlissnake
quelle
1

Die Antworten haben mir nicht geholfen, hier ist, was ich getan habe, aber ich hatte eine einzigartige Situation.

Um den Code zu bereinigen, verwende ich normalerweise dieses Format, bis ich bereit bin, die Komponente in eine andere Datei zu ziehen.

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

Dies führte jedoch dazu, dass der Fokus verloren ging, als ich den Code direkt in das Div einfügte, in dem es funktionierte.

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

Ich weiß nicht, warum das so ist, dies ist das einzige Problem, das ich beim Schreiben auf diese Weise hatte, und ich mache es in den meisten Dateien, die ich habe, aber wenn jemand etwas Ähnliches tut, verliert es deshalb den Fokus.

Kevin Danikowski
quelle
1

Ich hatte das gleiche Problem mit einer HTML-Tabelle, in der ich Textzeilen in einer Spalte eingegeben habe. Innerhalb einer Schleife lese ich ein JSON-Objekt und erstelle Zeilen. Insbesondere habe ich eine Spalte mit Eingabetext.

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

Ich habe es auf folgende Weise gelöst

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>
            );
          }
        }
Software Factory
quelle
0

Es stellte sich heraus, dass ich thisan die Komponente gebunden war, wodurch sie erneut gerendert wurde.

Ich dachte, ich würde es hier posten, falls jemand anderes dieses Problem hätte.

Ich musste mich ändern

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

Zu

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

Einfache Lösung , da in meinem Fall, ich habe nicht wirklich brauchte thisin renderField, aber hoffentlich Posting ich dies helfen wird , jemand anderes.

Matt D.
quelle
0

Ich wechselte die valueRequisite zu defaultValue. Das ist für mich in Ordnung.

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

// after
<input defaultValue={myVar} />
Chrisheyn
quelle
0

Mein Problem war, dass ich meinen Schlüssel dynamisch mit einem Wert des Elements benannte, in meinem Fall "Name", also war der Schlüssel key = { ${item.name}-${index}}. Wenn ich also die Eingabe mit item.name als Wert ändern wollte, änderte sich auch der Schlüssel und die Reaktion erkannte dieses Element nicht

Amogu
quelle
0

Ich hatte dieses Problem und es stellte sich heraus, dass ich eine funktionale Komponente verwendete und mich mit dem Status einer übergeordneten Komponente verband. Wenn ich zur Verwendung einer Klassenkomponente wechselte, verschwand das Problem. Hoffentlich gibt es einen Weg, dies zu umgehen, wenn funktionale Komponenten verwendet werden, da dies für einfache Item-Renderer et al. Viel bequemer ist.

Kyle Crossman
quelle
0

hat den nächsten Code in die Tag-Eingabe aufgenommen:

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

Vor:

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

Nach dem:

<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
quelle