React.js - entrée perdant le focus lors du rendu


97

J'écris juste pour entrer du texte et en onChangecas d'appels setState, React réaffiche mon interface utilisateur. Le problème est que l'entrée de texte perd toujours un focus, donc je dois le concentrer à nouveau pour chaque lettre: 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>
        );
    }

});

La seule raison qui se produirait est si a) quelque chose d'autre vole le focus, ou b) l'entrée scintille. Pourriez-vous fournir un jsfiddle / jsbin montrant le problème? Voici un jsbin de réaction de base .
Brigand

lol ... eh bien cela semble un peu ennuyeux: P Avec jquery, je définirais un identifiant pour le nouveau fichier d'entrée rendu, puis j'appellerais le focus dessus. Je ne sais pas à quoi ressemblerait le code en js. Mais je suis sûr que vous pouvez le faire :)
Medda86

1
@FakeRainBrigand: qu'entendez-vous par scintillement?
Krab

@Krab, comme si this.props.selected devenait faux, puis redevenait vrai. Cela entraînerait le démontage de l'entrée, puis le remontage.
Brigand

@Krab, essayez de supprimer les lignes slimScroll; cela pourrait faire quelque chose de bizarre qui cause des problèmes.
Brigand

Réponses:


87

Sans voir le reste de votre code, c'est une supposition. Lorsque vous créez un EditorContainer, spécifiez une clé unique pour le composant:

<EditorContainer key="editor1"/>

Lorsqu'un nouveau rendu se produit, si la même clé est vue, cela indiquera à React de ne pas écraser et de régénérer la vue, au lieu de la réutiliser. Ensuite, l'élément ciblé doit conserver le focus.


2
Cela a résolu mon problème avec le rendu d'un sous-composant contenant un formulaire d'entrée. Mais dans mon cas, j'avais besoin du contraire - le formulaire n'était pas re-rendu quand je le voulais. L'ajout de l'attribut clé a fonctionné.
Sam Texas

2
l'ajout d'une clé à l'entrée n'a pas aidé
Mïchael Makaröv

6
Peut-être que le (s) composant (s) contenant votre entrée a également été rendu. Vérifiez les clés sur les composants inclus.
Petr Gladkikh

Commentaire de vote favorable de @PetrGladkikh. J'avais besoin de clés sur tous les composants inclus.
Brian Cragun le

49

Je reviens ici encore et encore et trouve toujours la solution à mon ailleurs à la fin. Alors, je vais le documenter ici car je sais que je vais encore oublier ça!

La raison inputde ma perte de concentration dans mon cas était due au fait que je refaisais le renduinput changement d'état.

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

Donc, le problème est que je commence toujours à tout coder à un seul endroit pour tester rapidement et ensuite le diviser en modules séparés. Mais, ici, cette stratégie se retourne parce que la mise à jour de l'état sur le changement d'entrée déclenche la fonction de rendu et le focus est perdu.

Le correctif est simple, faites la modularisation depuis le début, en d'autres termes, " InputSortez le composant derender fonction"

Code fixe

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

Réf. à la solution: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947


1
J'utilisais un HOC dans la fonction de rendu, le déplacement de l'appel HOC vers la définition du composant a fait l'affaire. En quelque sorte similaire à cette réponse.
Mark E

3
Je pense que c'est mon problème, mais j'ai du mal à déplacer les composants en dehors de render()et à class ... extends Componentcause de la référence à this. par exempleonChange={this.handleInputChange}
Nateous

3
Le moral de l'histoire, pour les composants de la classe React, ne définit pas les composants à l'intérieur de la renderfonction, pour les composants fonctionnels React, ne définit pas les composants dans le corps de la fonction.
Wong Jia Hau

1
Merci! Tu as sauvé mes fesses !!
K-Sato

1
@Nateous J'ai eu exactement la même situation. Les appels HOC à l'intérieur de la renderfonction qui étaient passés à la méthode 'this.handleChange' et renvoyaient les composants à utiliser. Par exemple const TextAreaDataField = TextAreaDataFieldFactory(this.handleChange). J'ai simplement déplacé la création du composant vers le constructeur et y accéder dans la renderméthode en tant que this.TextAreaDataField. A fonctionné comme un charme.
Marcus Junius Brutus le

36

Si c'est un problème dans un routeur de réaction, <Route/>utilisez le renderprop à la place de component.

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


La perte de concentration se produit parce que l' componentaccessoire utiliseReact.createElement chaque fois au lieu de simplement restituer les modifications.

Détails ici: https://reacttraining.com/react-router/web/api/Route/component


2
C'était essentiellement mon problème. Plus précisément, je passais une fonction anonyme à l'accessoire du composant, déclenchant une perte de concentration:, <Route component={() => <MyComponent/>} />quand j'aurais dû le faire<Route component={MyComponent} />
Daniel

Vous m'avez sauvé la journée - aussi simple que cela!
Fabricio

12

Ma réponse est similaire à ce que @ z5h a dit.

Dans mon cas, j'avais l'habitude Math.random()de générer un uniquekey pour le composant.

Je pensais que le key n'était utilisé que pour déclencher un rerender pour ce composant particulier plutôt que pour re-rendre tous les composants de ce tableau (je renvoie un tableau de composants dans mon code). Je ne savais pas qu'il était utilisé pour restaurer l'état après le rendu.

Supprimer cela a fait le travail pour moi.


1
Un module comme npmjs.com/package/shortid est également un bon moyen de gérer quelque chose comme ça.
skwidbreth

4

L'application de l' autoFocusattribut à l' inputélément peut constituer une solution de contournement dans les situations où une seule entrée doit être ciblée. Dans ce cas, un keyattribut serait inutile car il ne s'agit que d'un élément et en outre, vous n'auriez pas à vous soucier de diviser l' inputélément en son propre composant pour éviter de perdre le focus sur le rendu du composant principal.


4

J'ai le même comportement.

Le problème dans mon code était que j'ai créé un tableau imbriqué d'éléments jsx comme celui-ci:

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

...

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

Chaque élément de ce tableau imbriqué est ré-rendu chaque fois que j'ai mis à jour l'élément parent. Et donc les entrées y perdent à chaque fois des accessoires "ref"

J'ai corrigé le problème avec transformer le tableau interne en un composant de réaction (une fonction avec une fonction de rendu)

const example = [
            <myComponentArray />
        ]

 ...

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

ÉDITER:

Le même problème apparaît lorsque je crée un React.Fragment

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

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

3

Je viens de rencontrer ce problème et je suis venu ici pour obtenir de l'aide. Vérifiez votre CSS! Le champ de saisie ne peut pas avoir user-select: none;ou il ne fonctionnera pas sur un iPad.


3

J'ai résolu le même problème en supprimant l'attribut clé dans l'entrée et ses éléments parents

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

Pour moi, cela était dû au rendu de la zone de saisie de recherche dans le même composant (appelé UserList) que la liste des résultats de la recherche. Ainsi, chaque fois que les résultats de la recherche changeaient, l'ensemble du composant UserList était renvoyé, y compris la zone de saisie.

Ma solution était de créer un tout nouveau composant appelé UserListSearch qui est distinct de UserList . Je n'avais pas besoin de définir des clés sur les champs de saisie dans UserListSearch pour que cela fonctionne. La fonction de rendu de mon UsersContainer ressemble maintenant à ceci:

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

Espérons que cela aide quelqu'un aussi.


2

Si le champ d'entrée est à l'intérieur d'un autre élément (c'est-à-dire, un élément conteneur comme <div key={"bart"}...><input key={"lisa"}...> ... </input></div>- les ellipses indiquant ici le code omis), il doit y avoir une clé unique et constante sur l'élément conteneur (ainsi que sur le champ d'entrée) . Sinon, React restitue un tout nouvel élément de conteneur lorsque l'état de l'enfant est mis à jour plutôt que de simplement restituer l'ancien conteneur. Logiquement, seul l'élément enfant doit être mis à jour, mais ...

J'ai eu ce problème en essayant d'écrire un composant qui a pris un tas d'informations d'adresse. Le code de travail ressemble à ceci

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

Les lecteurs attentifs remarqueront qu'il <fieldset>s'agit d'un élément contenant, mais qu'il ne nécessite pas de clé. La même chose vaut pour <>et <React.Fragment>ou même <div>pourquoi? Peut-être que seul le conteneur immédiat a besoin d'une clé. Je ne sais pas. Comme le disent les manuels de mathématiques, l'explication est laissée au lecteur sous forme d'exercice.


1

La raison principale est la suivante: lorsque React re-render, votre référence DOM précédente sera invalide. Cela signifie que react a changé l'arborescence DOM, et vous this.refs.input.focus ne fonctionnera pas, car l'entrée ici n'existe plus.


1

Les réponses fournies ne m'ont pas aidé, voici ce que j'ai fait mais j'avais une situation unique.

Pour nettoyer le code, j'ai tendance à utiliser ce format jusqu'à ce que je sois prêt à extraire le composant dans un autre fichier.

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

Mais cela lui a fait perdre le focus, quand j'ai mis le code directement dans le div, cela fonctionnait.

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

Je ne sais pas pourquoi c'est le seul problème que j'ai eu avec l'écriture de cette façon et je le fais dans la plupart des fichiers que j'ai, mais si quelqu'un fait une chose similaire, c'est pourquoi il perd le focus.


1

J'ai eu le même problème avec une table html dans laquelle j'ai des lignes de texte d'entrée dans une colonne. dans une boucle, je lis un objet json et je crée des lignes en particulier j'ai une colonne avec inputtext.

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

J'ai réussi à le résoudre de la manière suivante

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

Il s'est avéré que j'étais lié thisau composant qui provoquait sa restitution.

J'ai pensé que je le publierais ici au cas où quelqu'un d'autre aurait ce problème.

Je devais changer

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

À

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

Solution simple puisque dans mon cas, je ne l' ai pas vraiment besoin thisen renderField, mais je l' espère me affichant cela aidera quelqu'un d' autre.


0

Je suis passé à l' valuehélice defaultValue. Ça marche pour moi.

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

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

0

Mon problème était que j'ai nommé ma clé dynamiquement avec une valeur de l'élément, dans mon cas "nom" donc la clé était key = { ${item.name}-${index}}. Ainsi, lorsque je voulais changer l'entrée avec item.name comme valeur, la clé changerait également et donc réagir ne reconnaîtrait pas cet élément


0

J'ai eu ce problème et le problème s'est avéré être que j'utilisais un composant fonctionnel et que je me connectais à l'état d'un composant parent. Si je suis passé à l'utilisation d'un composant de classe, le problème a disparu. Espérons qu'il existe un moyen de contourner ce problème lors de l'utilisation de composants fonctionnels, car c'est beaucoup plus pratique pour les moteurs de rendu d'élément simples et al.


0

inclus le code suivant dans l'entrée de balise:

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

Avant:

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

Après:

<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)}
/>
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.