web-dev-qa-db-fra.com

Reactjs - Ajout de ref à l'entrée dans le rendu d'élément dynamique

J'essaie de concentrer/mettre en surbrillance le texte d'entrée surCliquez dans React. Il fonctionne comme prévu, mais uniquement sur le dernier élément du tableau rendu. J'ai essayé plusieurs méthodes différentes, mais elles font toutes exactement la même chose. Voici deux exemples de ce que j'ai:

export default class Services extends Component {

handleFocus(event) {
    event.target.select()
}

handleClick() {
    this.textInput.focus()
}


render() {
    return (
        <div>
            {element.sources.map((el, i) => (
                <List.Item key={i}>
                <Segment style={{marginTop: '0.5em', marginBottom: '0.5em'}}>
                    <Input fluid type='text'
                        onFocus={this.handleFocus}
                        ref={(input) => { this.textInput = input }} 
                        value='text to copy'
                        action={
                            <Button inverted color='blue' icon='copy' onClick={() => this.handleClick}></Button>
                        }
                    />
                </Segment>
                </List.Item>
            ))}
        </div>
    )
}

S'il n'y a qu'un seul élément en cours de rendu, il concentre le texte dans l'entrée, mais s'il y a plusieurs éléments, chaque clic sur le bouton de l'élément ne sélectionne que l'entrée du dernier élément. Voici un autre exemple:

export default class Services extends Component {

constructor(props) {
    super(props)

    this._nodes = new Map()
    this._handleClick = this.handleClick.bind(this)
}

handleFocus(event) {
    event.target.select()
}

handleClick(e, i) {
    const node = this._nodes.get(i)
    node.focus()
}


render() {
    return (
        <div>
            {element.sources.map((el, i) => (
                <List.Item key={i}>
                <Segment style={{marginTop: '0.5em', marginBottom: '0.5em'}}>
                    <Input fluid type='text'
                        onFocus={this.handleFocus}
                        ref={c => this._nodes.set(i, c)} 
                        value='text to copy'
                        action={
                            <Button inverted color='blue' icon='copy' onClick={e => this.handleClick(e, i)}></Button>
                        }
                    />
                </Segment>
                </List.Item>
            ))}
        </div>
    )
}

Ces deux méthodes répondent essentiellement de la même manière. J'ai besoin du focus d'entrée handleClick pour travailler pour chaque élément rendu dynamiquement. Tout conseil est grandement appréciée. Merci d'avance!

Le composant Input est importé de l'interface utilisateur sémantique React sans implémentations supplémentaires dans mon application

MISE À JOUR Merci les gars pour les bonnes réponses. Les deux méthodes fonctionnent très bien dans un rendu d'élément de boucle unique, mais maintenant j'essaie de l'implémenter avec plusieurs éléments parents. Par exemple:

import React, { Component } from 'react'
import { Button, List, Card, Input, Segment } from 'semantic-ui-react'

export default class ServiceCard extends Component {

handleFocus(event) {
    event.target.select()
}

handleClick = (id) => (e) => {
    this[`textInput${id}`].focus()
}

render() {
    return (
        <List divided verticalAlign='middle'>
            {this.props.services.map((element, index) => (
                <Card fluid key={index}>
                    <Card.Content>
                        <div>
                            {element.sources.map((el, i) => (
                                <List.Item key={i}>
                                    <Segment>
                                        <Input fluid type='text'
                                            onFocus={this.handleFocus}
                                            ref={input => { this[`textInput${i}`] = input }} 
                                            value='text to copy'
                                            action={
                                                <Button onClick={this.handleClick(i)}></Button>
                                            }
                                        />
                                    </Segment>
                                </List.Item>
                            ))}
                        </div>
                    </Card.Content>
                </Card>
            ))}
        </List>
    )
}

Maintenant, dans le code modifié, vos méthodes fonctionnent très bien pour un élément Card, mais lorsqu'il y a plusieurs éléments Card, cela ne fonctionne que pour le dernier. Les deux InputButtons fonctionnent respectivement pour leurs entrées, mais uniquement sur le dernier élément Card rendu.

9
merrilj

Vous définissez un ref dans une boucle, comme vous le savez déjà, le ref est défini sur le class via le mot clé this. Cela signifie que vous définissez plusieurs refs mais que vous remplacez le même à l'intérieur du class.
Une solution (pas la solution idéale) consiste à les nommer différemment, peut-être ajouter la clé à chaque nom ref:

        ref={input => {
          this[`textInput${i}`] = input;
        }}

et lorsque vous ciblez cet événement onClick de Button, vous devez utiliser la même clé comme paramètre:

 action={
                  <Button
                    inverted
                    color="blue"
                    icon="copy"
                    onClick={this.handleClick(i)}
                  >
                    Focus
                  </Button>
                }

Maintenant, l'événement click doit changer et accepter le id comme paramètre et déclencher le ref approprié (j'utilise le currying ici):

  handleClick = (id) => (e) => {
      this[`textInput${id}`].focus();
  }

Notez que c'est une solution plus facile mais pas la solution idéale, car nous créons une nouvelle instance d'une fonction sur chaque rendu, donc nous passons un nouvel accessoire qui peut interrompre l'algorithme différent de react (un meilleur et plus " react'ish " chemin à venir ensuite).

Avantages:

  • Plus facile à mettre en œuvre
  • Mise en œuvre plus rapide

Inconvénients:

  • Peut entraîner des problèmes de performances
  • Moins la manière de réagir des composants

Exemple de travail

Voici le code complet:

class Services extends React.Component {

  handleFocus(event) {
    event.target.select();
  }


  handleClick = id => e => {
    this[`textInput${id}`].focus();
  };

  render() {
    return (
      <div>
        {sources.map((el, i) => (
          <List.Item key={i}>
            <Segment style={{ marginTop: "0.5em", marginBottom: "0.5em" }}>
              <Input
                fluid
                type="text"
                onFocus={this.handleFocus}
                ref={input => {
                  this[`textInput${i}`] = input;
                }}
                value="text to copy"
                action={
                  <Button
                    inverted
                    color="blue"
                    icon="copy"
                    onClick={this.handleClick(i)}
                  >
                    Focus
                  </Button>
                }
              />
            </Segment>
          </List.Item>
        ))}
      </div>
    );
  }
}

render(<Services />, document.getElementById("root"));

Une solution meilleure et plus "react'ish" consisterait à utiliser la composition des composants ou un HOC qui enveloppe le Button et à injecter une logique simple, comme passer le id au lieu d'utiliser 2 fonctions dans le parent.

Avantages:

  • Comme mentionné, moins de chances de problèmes de performances
  • Vous pouvez réutiliser ce composant et cette logique
  • Parfois plus facile à déboguer

Inconvénients:

  • Plus d'écriture de code

  • Un autre composant pour maintenir/tester etc ..

n exemple de travail
Le code complet:

class MyButton extends React.Component {

  handleClick = (e) =>  {
    this.props.onClick(this.props.id)
  }

  render() {
    return (
      <Button
      {...this.props}
        onClick={this.handleClick}
      >
        {this.props.children}
      </Button>
    )
  }
}


class Services extends React.Component {

  constructor(props){
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleFocus(event) {
    event.target.select();
  }


  handleClick(id){
    this[`textInput${id}`].focus();
  };

  render() {
    return (
      <div>
        {sources.map((el, i) => (
          <List.Item key={i}>
            <Segment style={{ marginTop: "0.5em", marginBottom: "0.5em" }}>
              <Input
                fluid
                type="text"
                onFocus={this.handleFocus}
                ref={input => {
                  this[`textInput${i}`] = input;
                }}
                value="text to copy"
                action={
                  <MyButton
                    inverted
                    color="blue"
                    icon="copy"
                    onClick={this.handleClick}
                    id={i}
                  >
                    Focus
                  </MyButton>
                }
              />
            </Segment>
          </List.Item>
        ))}
      </div>
    );
  }
}

render(<Services />, document.getElementById("root"));

Modifier
Pour faire suite à votre modification:

mais lorsqu'il y a plusieurs éléments Card, cela ne fonctionne que pour le dernier.

Cela se produit pour la même raison que précédemment, vous utilisez le même i pour les deux tableaux.
Ceci est une solution simple, utilisez à la fois index et i pour vos noms ref.
Définition du nom ref:

ref={input => { this[`textInput${index}${i}`] = input }}

Passer le nom au gestionnaire:

<Button onClick={this.handleClick(`${index}${i}`)}></Button>

Exemple de travail

J'ai modifié ma question et fourni une deuxième solution considérée comme la meilleure pratique. relisez ma réponse et voyez les différentes approches.

8
Sagiv b.g