Material-ui: [TextField] perd le focus sur le rendu

Créé le 8 juin 2015  ·  41Commentaires  ·  Source: mui-org/material-ui

Si un champ de texte qui avait le focus est rendu par setState , il perd le focus.

question v0.x

Commentaire le plus utile

@scotmatson Je peux confirmer que, au moins dans mon cas, le problème est résolu si je transforme le Container = styled.div que j'ai comme wrapper en un composant normal et non stylisé div .

Éditer:
Et je partage également vos frustrations. Ce serait vraiment bien si ces deux bibliothèques fonctionnaient parfaitement ensemble. Ce problème et le problème de la spécificité sont deux choses qui, j'espère, seront bientôt réglées, mais je suis reconnaissant aux personnes qui ont donné de leur temps.

Edit2:
Je viens de découvrir ce qui causait le problème dans mon cas: déclarer des composants stylisés dans la méthode de rendu! Cela provoquait la recréation des composants à chaque re-rendu et React ne pouvait pas suivre quel élément avait le focus à travers la limite de re-rendu. Cela a également conduit à quelques autres problèmes de rendu pour moi.

Le déplacement de toutes mes déclarations de composants stylisés vers l'extérieur de mon composant a résolu les problèmes de perte de focus pour moi. Probablement une erreur de noob, mais personne ne l'a mentionné ici, alors j'ai pensé que je reviendrais faire un rapport.

Tous les 41 commentaires

+1

Je dois utiliser l'événement onBlur avec defaultValue pour que les Textfields fonctionnent correctement.

quelqu'un a une solution de contournement pour que cela fonctionne?

Je ne pense plus que ce soit un problème. Voir le code ci-dessous:

//taken from examples/webpack-example

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

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

const Main = React.createClass({

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

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

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

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

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

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

  render() {

    console.log(this.state.counter);

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

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

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

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

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

      </div>
    );
  },

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

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

});

module.exports = Main;


Voici l'application en action (avec l'état mis à jour enregistré dans la console à mesure que l'entrée TextField change). Vous pouvez voir que le TextField conserve le focus:

4uo63bzhmj

J'ai toujours ça. L' exemple
reproduire l'utilisation
<TextField ref="textField" value={this.state.valueToBeEdited} onChange = {this._incrementStateCounter}/>

ou

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

Je vois également ce qui semble être une régression de cette question. Mes TextFields perdent le focus sur le rendu. Je mets la prop value du TextField , donc c'est un composant contrôlé.

Je remarque un nouvel avertissement dans la console: «TextField est en train de changer une entrée non contrôlée de type texte à contrôler». Je ne me souviens pas avoir vu cela auparavant, je ne sais pas si c'est pertinent ici ou non. L'avertissement apparaît une fois que j'entre le premier caractère dans le TextField .

Grâce à un petit débogage, il semble que quelque chose fasse perdre le focus au TextField . Le gestionnaire interne onBlur est appelé, ce qui fait passer l'état interne isFocused du TextField à false. Si je connecte document.activeElement pendant le gestionnaire onBlur (pour déterminer quel composant a volé le focus), alors il enregistre la racine du document (corps).

J'ai pu affiner davantage cela en déterminant que la source de l'événement de flou était le premier MenuItem dans un Menu ailleurs dans l'application. La définition de la propriété disableAutoFocus sur true résolu mon problème. J'espère que ceci aide quelqu'un d'autre.

J'ai ouvert un autre numéro pour capturer ceci: # 4387.

Je peux confirmer que c'est toujours un problème ...

Le voir dans un formulaire de connexion très simple - j'utilise ceci en conjonction avec des formulaires redux et lorsque j'interagis avec le formulaire, cela provoque un nouveau rendu (touché un champ dans le langage du formulaire redux) qui perd le focus.

En guise de solution de contournement, je ne re-rende le formulaire réel que s'il y a eu un changement dans l'état d'erreur.

Vous pouvez reproduire facilement ce problème en mettant le TextField dans un Table défini comme selectable=true . Chaque fois que vous cliquez sur un champ de texte pour commencer à le modifier, la ligne obtiendra le focus, changeant ainsi sa couleur d'arrière-plan (je suppose que certains props de la ligne pourraient être définis comme selected=true ), donc déclencher un nouveau rendu, vous perdrez ainsi le focus ...

Donc, vous ne pouvez pas utiliser un TextField dans un selectable Table , pas sûr que ce soit une bonne pratique UX de toute façon, mais quand même :)

Nous avons remarqué que sur notre projet, je peux essayer de pousser un reproducteur.

Un travail autour de cela? Remarquer également cela lors de l'utilisation d'une zone de texte d'entrée normale à l'intérieur d'un tableau.

@ dalexander01 J'ai trouvé une "solution", mais elle semble très spécifique à la forme redux car elle rencontrait le même problème en n'utilisant pas les composants d'entrée md. Si c'est utile, je peux publier un extrait!

@deyceg s'il vous plaît faites, ça ne peut pas faire de mal 😄. Rencontrez simplement ce problème avec cette combinaison de <Dialog/> et de <TextField/> .

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

Je l'ai résolu pour mon cas, vous devez fournir un identifiant unique à ce champ de texte.
...
L'identifiant ne devrait pas changer après le nouveau rendu. De cette façon, React peut garder une trace de quel élément a été focalisé ou non.
PS J'avais des champs de texte dans un tableau qui est rendu via myArray.map, là vous devez fournir les mêmes clés lors du rendu.

EDIT: J'ai testé à nouveau, le simple fait d'avoir la "clé" sur myArray pour être la même entre les rerenders a résolu le problème.
L'identifiant du champ de texte a été changé en shortid.generate ().

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

Quelqu'un a réussi à trouver une solution de contournement à ce problème (sans utiliser refs )? Cela semble être la combinaison de onChange et de la définition de la propriété value sur une clé d'état qui rend l'entrée lâche.

J'ai également eu le problème suivant, je l'ai résolu en prenant l'implémentation SelectField si vous le voyez en interne, c'est appeler un DropdownMenu https://github.com/callemall/material-ui/blob/ccf712c5733508784cd709c18c29059542d6aad1/src/SelectField/SelectField. js # L192

donc j'en ai également besoin pour ajouter un DropDownMenu qui encapsule en interne les composants dans un menu, j'ai ajouté disableAutoFocus pour que mon TextField garde le focus dessus comme indiqué ci-dessous.

Peut-être pouvons-nous exposer un composant DropDownMenu à partir de SelectField et permettre la désactivation de la mise au point automatique avec des accessoires.

out

Une solution de contournement pour cela pour ceux qui sont aux prises avec cela et qui veulent juste que quelque chose fonctionne, bien que cela puisse ne pas s'appliquer dans certaines situations et pourrait présenter des problèmes (je ne suis pas tout à fait sûr moi-même cependant) est d'utiliser plutôt une variable que l’État. Donc, au lieu de this.state.value ce serait this.value que onChange mettrait à jour. Le composant n'aurait pas value prop onChange vous utiliserez e.target.value .

La seule différence que je connais entre une variable sur l'instance et l'état est que l'instance var ne déclenchera pas un nouveau rendu. Bien qu'il puisse y avoir d'autres raisons, j'espère que d'autres vont me rattraper sur celle-ci.

Je reçois toujours cette erreur. Des travaux autour de vous?

La solution de contournement que j'ai trouvée consistait à remplacer la fonction shouldComponentUpdate sur mon composant, puis à renvoyer false lorsque j'ai modifié les valeurs d'état utilisées par mes champs de texte.

`shouldComponentUpdate(nextProps, nextState) {    

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

      return false;  

    }

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

      return false;  

    }  
    else return true;  
    }

»

Cela a résolu mon problème. Le problème semble être le rendu du composant à chaque changement d'état, qui réinitialise ensuite le focus. Ainsi, en utilisant la fonction ci-dessus pour désactiver le rendu lorsque vous effectuez un événement onChange pour définir des valeurs d'état, le composant ne se recharge pas.

@ Aspintyl000 cela fonctionne ... un peu. Lorsque je règle la valeur du champ de texte via le prop «value», et que je règle cet accessoire sur «this.state.value», rien ne s'affiche. Bien que, lorsque je supprime cela, cela semble fonctionner.

J'utilise des champs de texte dans une ligne de tableau, et c'est un problème: (des progrès?

Psid.txt

J'obtenais également la même erreur. Mais après avoir utilisé componentDidMount () {
this.refs.textField.focus ();
}
il ne perd pas de vue. Mais il n'affiche pas la valeur d'entrée.

J'ai essayé d'ajouter <TextField /> dans un <Table /> . Comme dans les situations ci-dessus, je ne peux pas concentrer l'entrée.
Ma solution: ajouter un événement onClick au , lorsque je clique dessus, définissez le selectable=false la table. Après votre opération de saisie, utilisez un événement onBlue pour définir le selectable=true la table.

Comportement assez odieux.

J'avais vécu cela après avoir enveloppé deux boîtes de saisie avec un composant stylisé. La division externe a complètement cassé le comportement. C'est un défaut terrible dans la conception de cette bibliothèque.

C'est donc une solution de contournement assez terrible, mais les travaux suivants fonctionnent principalement:

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

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

};
`` ``

Ouais, pas joli ...

@scotmatson " styled-components cependant.

@scotmatson Je peux confirmer que, au moins dans mon cas, le problème est résolu si je transforme le Container = styled.div que j'ai comme wrapper en un composant normal et non stylisé div .

Éditer:
Et je partage également vos frustrations. Ce serait vraiment bien si ces deux bibliothèques fonctionnaient parfaitement ensemble. Ce problème et le problème de la spécificité sont deux choses qui, j'espère, seront bientôt réglées, mais je suis reconnaissant aux personnes qui ont donné de leur temps.

Edit2:
Je viens de découvrir ce qui causait le problème dans mon cas: déclarer des composants stylisés dans la méthode de rendu! Cela provoquait la recréation des composants à chaque re-rendu et React ne pouvait pas suivre quel élément avait le focus à travers la limite de re-rendu. Cela a également conduit à quelques autres problèmes de rendu pour moi.

Le déplacement de toutes mes déclarations de composants stylisés vers l'extérieur de mon composant a résolu les problèmes de perte de focus pour moi. Probablement une erreur de noob, mais personne ne l'a mentionné ici, alors j'ai pensé que je reviendrais faire un rapport.

Le problème que je semble avoir est que le focus est beaucoup chaque fois qu'un TextFields (ou Input) est rendu dans un object.map (). Les TextFields en dehors des maps fonctionnent parfaitement. Est-ce que je manque des accessoires ou autre chose? Je suis totalement perdu

J'ai essayé presque toutes les solutions suggérées dans ce fil sans aucune chance.

La structure approximative est la suivante

Fichier principal

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

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

NewUxCard

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

A noter que j'utilise Redux-React, donc les changements d'état passent par input-> action-> reducer

Des idées? Merci

MODIFIER 1:
Enquête plus approfondie sur le problème, je l'ai réduit et résolu en supprimant la fonction de réaction Steps (props) utilisée pour créer un tag(dans ce cas).

Alors avant, la structure était-> Fonction Steps (props) -> Mapping Class. Alors maintenant, j'ai supprimé la fonction (l'homme du milieu) et juste Const Steps -> Mapping Class. Je ne peux que spéculer sur les raisons pour lesquelles cela fonctionne (peut-être que certains accessoires ont été ignorés), mais cela a résolu mon cas.

@piltsen Je viens de rencontrer ce problème sur la v1.5.0 lors du rendu de TextFields dans un .map() - Je l'ai réduit à ma clé TextField ayant la même valeur que l'entrée, donc quand elle a changé, cela a probablement provoqué le montage du TextField à nouveau et perdre la concentration. Je ne sais pas si cela vous aide - même si je suis sûr que maintenant vous l'avez trié

C'est également un symptôme de l'utilisation des accessoires de rendu. Faire quelque chose comme

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

peut provoquer ce comportement. Pour corriger, transmettez un fragment à la place si cela est autorisé par la conception:

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

Dans mon cas, la mise au point n'a pas été perdue mais le curseur disparaît de TextField. Je peux vérifier que document.activeElement est égal à mon TextField, je peux utiliser la commande clavier et la molette de la souris pour changer la valeur (numéro de type), mais chaque rendu d'exécution de réducteur de type se produit et le curseur est perdu.

Ensuite, en haut du rendu, je recherche par identifiant mon TextField et fais

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

, ce qui n'aide pas. Mais quand je fais focus () avec un certain délai , cela semble fonctionner.

Éditer:
Eh bien, à l'intérieur de HOC, cela ne fonctionne toujours pas, il semble donc nécessaire de le pousser de toute façon.

@ younes0 Je peux confirmer que cela résout le problème pour moi aussi. Dans mon cas, j'avais un composant d'aide sans état qui retournait un TextField avec quelques accessoires communs, puis répartissait le reste des accessoires dessus, donc je n'avais pas à spécifier des choses comme className ou variant plusieurs fois sur l'ensemble du formulaire. Mes champs de texte perdaient le focus dès qu'une modification était apportée au formulaire, et la suppression de la fonction d'assistance et le simple fait de créer des TextField bruts encore et encore le corrigeaient.

Cela peut-il être rouvert ou un nouveau numéro doit-il être déposé?

Je reçois toujours ce problème, j'utilise 3.2.0

@OmarDavilaP Pouvez-vous créer un nouveau numéro avec une reproduction?

Je l'ai résolu pour mon cas, vous devez fournir un identifiant unique à ce champ de texte.
...
L'identifiant ne devrait pas changer après le nouveau rendu. De cette façon, React peut garder une trace de quel élément a été focalisé ou non.
PS J'avais des champs de texte dans un tableau qui est rendu via myArray.map, là vous devez fournir les mêmes clés lors du rendu.

EDIT: J'ai testé à nouveau, le simple fait d'avoir la "clé" sur myArray pour être la même entre les rerenders a résolu le problème.
L'identifiant du champ de texte a été changé en shortid.generate ().

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

Oui, cela résout le problème dans mon cas, en gros j'utilisais:

<NewTextField key={user.id+Math.random()}>
Je l'ai remplacé par:

<NewTextField key={user.id}>
Donc, oui, cela semble une mauvaise pratique de générer des clés aléatoires à chaque fois qu'il re-rend le composant.

Pour moi, le problème était que j'ai créé une fonction wrapper pour TextField, mais j'ai déclaré cette fonction dans la fonction mon composant, une fois déplacé en dehors de ma fonction, tout fonctionnait comme prévu!

J'ai créé un exemple de sendbox https://codesandbox.io/s/material-demo-msbxl?fontsize=14&hidenavigation=1&theme=dark

J'ai ce problème avec le rendu conditionnel. J'ai un objet javascript dans le rendu qui renvoie divers composants dans le fichier .js. Je ne le décris pas correctement, désolé. Quand je le réparerai, je modifierai ce message, si je me souviens bien.

edit: Ouais, je l'ai réparé! Je crois que la façon dont je rendais conditionnellement rendait chaque composant même lorsqu'il n'était pas affiché. J'ai déplacé la logique en dehors du rendu et cela semble fonctionner correctement maintenant. Voici l'ancien code:

edit: je ne peux pas styler sur cette chose, alors tant pis.

J'ai rencontré ce problème aujourd'hui et je me suis cogné la tête dessus pendant quelques bonnes heures. Dans mon cas, ce que je faisais mal était d'envelopper le TextField dans un composant sans état pour mieux organiser ma page (ne pas en rendre si les données se chargent, etc etc), mais j'ai laissé le useState callback qui contrôlait le TextField dans le composant parent.

Pour la postérité, voici un petit lien codesandbox pour reproduire ma stupidité: https://codesandbox.io/s/mui-textedit-losefocus-g6xxb

La question est maintenant de savoir comment créer mon composant personnalisé enveloppant TextField ? HOC et createElement semblent se produire dans ce bogue:

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

Nevermind, l'approche ci-dessus fonctionne bien - j'ai réalisé que j'avais été défini le composant personnalisé à l'intérieur du composant parent, ce qui a causé le problème. Lorsqu'il est déplacé en dehors de la portée parent (comme il se doit), cela fonctionne bien - j'ai mis à jour le bac à sable précédent pour afficher la bonne approche: https://codesandbox.io/s/mui-textedit-losefocus-g6xxb

Pour moi, le problème était que j'ai créé une fonction wrapper pour TextField, mais j'ai déclaré cette fonction dans la fonction mon composant, une fois déplacé en dehors de ma fonction, tout fonctionnait comme prévu!

J'ai créé un exemple de sendbox https://codesandbox.io/s/material-demo-msbxl?fontsize=14&hidenavigation=1&theme=dark

C'était mon problème. Je vous remercie!

Nevermind, l'approche ci-dessus fonctionne bien - j'ai réalisé que j'avais été défini le composant personnalisé à l'intérieur du composant parent, ce qui a causé le problème. Lorsqu'il est déplacé en dehors de la portée parent (comme il se doit), cela fonctionne bien - j'ai mis à jour le bac à sable précédent pour afficher la bonne approche: https://codesandbox.io/s/mui-textedit-losefocus-g6xxb

Je vous remercie. Cela a fonctionné!

Pour moi, le problème était que j'ai créé une fonction wrapper pour TextField, mais j'ai déclaré cette fonction dans la fonction mon composant, une fois déplacé en dehors de ma fonction, tout fonctionnait comme prévu!

J'ai créé un exemple de sendbox https://codesandbox.io/s/material-demo-msbxl?fontsize=14&hidenavigation=1&theme=dark

@ fleischman718 merci d'avoir trouvé celui-ci! La suppression de la fonction de composant d'habillage pour un TextField à l'intérieur de la définition d'un composant parent a résolu mon problème!

Il m'a fallu une seconde pour comprendre ce qui se passait dans le bac à sable, alors j'ai mis un résumé ci-dessous:

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

const ParentComponent = () => {

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

  return (
    <div>
      <TextFieldWrapper />
    </div>
  )
}
Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

sys13 picture sys13  ·  3Commentaires

FranBran picture FranBran  ·  3Commentaires

chris-hinds picture chris-hinds  ·  3Commentaires

mattmiddlesworth picture mattmiddlesworth  ·  3Commentaires

ryanflorence picture ryanflorence  ·  3Commentaires