Comment générer des identifiants uniques pour les étiquettes de formulaire dans React?

129

J'ai des éléments de formulaire avec labels et je souhaite avoir des identifiants uniques pour lier les labels aux éléments avec htmlForattribut. Quelque chose comme ça:

React.createClass({
    render() {
        const id = ???;
        return (
            <label htmlFor={id}>My label</label>
            <input id={id} type="text"/>
        );
    }
});

J'avais l'habitude de générer des identifiants basés sur this._rootNodeIDmais ce n'est pas disponible depuis React 0.13. Quelle est la meilleure et / ou la plus simple façon de le faire maintenant?

Artem Sapegin
la source
si vous générez cet élément encore et encore, je suppose que dans une instruction for, pourquoi ne pas utiliser l'itérateur dessus? Je suppose que vous pouvez également appeler une fonction qui génère un guid unique si un numéro d'index n'est pas assez bon. stackoverflow.com/questions/105034/…
Chris Hawkes
1
Il existe de nombreux éléments de formulaire différents dans différents composants et tous doivent avoir des ID uniques. La fonction pour générer des identifiants est ce à quoi j'ai pensé et ce que je vais faire si personne ne suggère une meilleure solution.
Artem Sapegin
3
Vous pouvez stocker un compteur incrémentiel «global» quelque part et l'utiliser. id = 'unique' + (++GLOBAL_ID);var GLOBAL_ID=0;?
WiredPrairie
1
Je sais que je suis très, très en retard à cette fête, mais une autre alternative est d'envelopper l'entrée dans l'étiquette au lieu d'utiliser des identifiants, par exemple:<label>My label<input type="text"/></label>
Mike Desjardins

Réponses:

85

Cette solution fonctionne très bien pour moi.

utils/newid.js:

let lastId = 0;

export default function(prefix='id') {
    lastId++;
    return `${prefix}${lastId}`;
}

Et je peux l'utiliser comme ceci:

import newId from '../utils/newid';

React.createClass({
    componentWillMount() {
        this.id = newId();
    },
    render() {
        return (
            <label htmlFor={this.id}>My label</label>
            <input id={this.id} type="text"/>
        );
    }
});

Mais cela ne fonctionnera pas dans les applications isomorphes.

Ajouté le 17.08.2015 . Au lieu de la fonction newId personnalisée, vous pouvez utiliser uniqueId de lodash.

Mis à jour le 28.01.2016 . Il est préférable de générer une pièce d'identité componentWillMount.

Artem Sapegin
la source
3
Parce qu'il recommencera à générer des identifiants à partir du 1er dans le navigateur. Mais en fait, vous pouvez utiliser différents préfixes sur le serveur et dans le navigateur.
Artem Sapegin
7
Ne fais pas ça render! Créer l'identifiant danscomponentWillMount
sarink
1
Vous avez créé un conteneur avec état, mais vous négligez d'utiliser setState et enfreignez la spécification pour render. facebook.github.io/react/docs/component-specs.html . Cela devrait être assez facile à réparer.
aij
3
J'utilise uniqueId de lodash dans le constructeur et j'utilise setState pour définir l'id. Fonctionne bien pour mon application client uniquement.
CrossProduct
1
componentWillMountest obsolète, faites-le plutôt dans le constructeur. Voir: reactjs.org/docs/react-component.html#unsafe_componentwillmount
Vic
79

L'identifiant doit être placé à l'intérieur de componentWillMount (mise à jour pour 2018) constructor, pas render. Le mettre en rendergénèrera inutilement de nouveaux identifiants.

Si vous utilisez un trait de soulignement ou un lodash, il existe une uniqueIdfonction, donc votre code résultant devrait être quelque chose comme:

constructor(props) {
    super(props);
    this.id = _.uniqueId("prefix-");
}

render() { 
  const id = this.id;
  return (
    <div>
        <input id={id} type="checkbox" />
        <label htmlFor={id}>label</label>
    </div>
  );
}

Mise à jour des Hooks 2019:

import React, { useState } from 'react';
import _uniqueId from 'lodash/uniqueId';

const MyComponent = (props) => {
  // id will be set once when the component initially renders, but never again
  // (unless you assigned and called the second argument of the tuple)
  const [id] = useState(_uniqueId('prefix-'));
  return (
    <div>
      <input id={id} type="checkbox" />
      <label htmlFor={id}>label</label>
    </div>
  );
}
couler
la source
11
Ou vous pouvez également le mettre dans le constructeur.
John Weisz
componentWillMount est obsolète depuis React 16.3.0, utilisez plutôt UNSAFE_componentWillMount, voir reactjs.org/docs/react-component.html#unsafe_componentwillmount
lokers
2
Quelqu'un peut-il suggérer comment cela devrait être fait avec les nouveaux Hooks dans React 16.8?
Aximili
4
Comme vous ne suivez pas la valeur de l'identifiant, vous pouvez également utiliserconst {current: id} = useRef(_uniqueId('prefix-'))
forivall
1
Quelle est la différence avec l'utilisation de useRef au lieu d'utiliser State?
XPD
24

Suite à partir du 04/04/2019, cela semble pouvoir être accompli avec les React Hooks useState:

import React, { useState } from 'react'
import uniqueId from 'lodash/utility/uniqueId'

const Field = props => {
  const [ id ] = useState(uniqueId('myprefix-'))

  return (
    <div>
      <label htmlFor={id}>{props.label}</label>
      <input id={id} type="text"/>
    </div>
  )      
}

export default Field

Si je comprends bien, vous ignorez le deuxième élément du tableau dans la déstructuration du tableau qui vous permettrait de mettre à jour id, et vous avez maintenant une valeur qui ne sera pas mise à jour à nouveau pendant la durée de vie du composant.

La valeur de idsera myprefix-<n><n>est une valeur entière incrémentielle renvoyée uniqueId. Si ce n'est pas assez unique pour vous, pensez à créer le vôtre

function gen4() {
  return Math.random().toString(16).slice(-4)
}

function simpleUniqueId(prefix) {
  return (prefix || '').concat([
    gen4(),
    gen4(),
    gen4(),
    gen4(),
    gen4(),
    gen4(),
    gen4(),
    gen4()
  ].join(''))
}

ou consultez la bibliothèque que j'ai publiée avec ceci ici: https://github.com/rpearce/simple-uniqueid . Il existe également des centaines ou des milliers d'autres éléments d'identification uniques, mais les lodash uniqueIdavec un préfixe devraient suffire pour faire le travail.


Mise à jour 10/07/2019

Merci à @Huong Hk de m'avoir pointé vers l'état initial paresseux des hooks , dont la somme est que vous pouvez passer une fonction à useStatequi ne sera exécutée que sur le montage initial.

// before
const [ id ] = useState(uniqueId('myprefix-'))

// after
const [ id ] = useState(() => uniqueId('myprefix-'))
rpearce
la source
1
J'ai les mêmes problèmes avec le rendu du serveur, comme beaucoup d'autres méthodes, mentionnées sur cette page: le composant sera renvoyé avec un nouvel identifiant dans le navigateur.
Artem Sapegin
@ArtemSapegin: il y avait un problème ( github.com/facebook/react/issues/1137 ) sur le projet React discutant de la possibilité d'avoir des composants avec des identifiants uniques, mais je ne pense pas qu'il en soit résulté . Dans quelle mesure les identifiants générés sont-ils les mêmes entre le serveur et le client? Je pense que pour un <input />, ce qui importe, c'est que les attributs htmlForet iddoivent être liés, quelles que soient les valeurs.
rpearce
Il est important d'éviter les mises à jour DOM inutiles, que de nouveaux ID entraîneront.
Artem Sapegin
6
C'est mieux si vous fournissez une fonction comme initialState# 1 const [ id ] = useState(() => uniqueId('myprefix-'))au lieu du résultat d'une fonction # 2 const [ id ] = useState(uniqueId('myprefix-')) L'état: iddes 2 façons ci-dessus ne sont pas différentes. Mais le différent uniqueId('myprefix-')sera exécuté une fois (# 1) au lieu de chaque nouveau rendu (# 2). Voir: État initial paresseux: reactjs.org/docs/hooks-reference.html#lazy-initial-state Comment créer des objets coûteux paresseusement?: Reactjs.org/docs
Huong Nguyen
1
@HuongHk c'est incroyable; Je ne savais pas! Je vais mettre à jour ma réponse
rpearce
4

Vous pouvez utiliser une bibliothèque telle que node-uuid pour cela afin de vous assurer d'obtenir des identifiants uniques.

Installez en utilisant:

npm install node-uuid --save

Ensuite, dans votre composant de réaction, ajoutez ce qui suit:

import {default as UUID} from "node-uuid";
import {default as React} from "react";

export default class MyComponent extends React.Component {   
  componentWillMount() {
    this.id = UUID.v4();
  }, 
  render() {
    return (
      <div>
        <label htmlFor={this.id}>My label</label>
        <input id={this.id} type="text"/>
      </div>
    );
  }   
}
Stuart Ervine
la source
2
La réponse semble avoir été mise à jour pour se conformer aux spécifications
Jonas Berlin
2
Cela ne fonctionne pas dans les applications isomorphes, car l'id généré sur le serveur est différent de l'id généré sur le client.
Daniel T.
2
Mais c'est indiqué dans le cadre de la réponse, ce qui est très trompeur
Tom McKenzie
1
Ya, -1 pour utiliser des identifiants UNIVERSELLEMENT uniques, c'est un marteau de taille universelle pour un clou de taille mondiale.
Jon z
1

J'espère que cela sera utile à tous ceux qui recherchent une solution universelle / isomorphe, car le problème de la somme de contrôle est ce qui m'a amené ici en premier lieu.

Comme indiqué ci-dessus, j'ai créé un utilitaire simple pour créer séquentiellement un nouvel identifiant. Étant donné que les ID continuent d'augmenter sur le serveur et recommencent à partir de 0 dans le client, j'ai décidé de réinitialiser l'incrément à chaque démarrage du SSR.

// utility to generate ids
let current = 0

export default function generateId (prefix) {
  return `${prefix || 'id'}-${current++}`
}

export function resetIdCounter () { current = 0 }

Et puis dans le constructeur ou componentWillMount du composant racine, appelez la réinitialisation. Cela réinitialise essentiellement la portée JS du serveur dans chaque rendu de serveur. Chez le client, cela n'a pas (et ne devrait pas) avoir d'effet.

ténor528
la source
vous pourriez toujours avoir des conflits d'ID si les clients recommencent à nommer les entrées à partir de 0.
Tomasz Mularczyk
@Tomasz vous voulez que le client recommence sur le formulaire 0 pour que les sommes de contrôle correspondent.
ténor528
0

Pour les utilisations habituelles de labelet input, il est simplement plus facile d'encapsuler l'entrée dans une étiquette comme celle-ci:

import React from 'react'

const Field = props => (
  <label>
    <span>{props.label}</span>
    <input type="text"/>
  </label>
)      

Cela permet également dans les cases à cocher / boutons radio d'appliquer un remplissage à l'élément racine et d'obtenir toujours des commentaires sur le clic sur l'entrée.

Developia
la source
1
+1 pour la simplicité et utile dans certains cas, -1 non utilisable avec, par exemple select, des étiquettes multiples sur différentes positions, des composants d'interface utilisateur non couplés, etc., l'utilisation d'id est également recommandée a11y: En général, les étiquettes explicites sont mieux prises en charge par la technologie d'assistance, w3. org / WAI / tutorials / forms / labels /…
Michael B.
-1

J'ai trouvé une solution simple comme celle-ci:

class ToggleSwitch extends Component {
  static id;

  constructor(props) {
    super(props);

    if (typeof ToggleSwitch.id === 'undefined') {
      ToggleSwitch.id = 0;
    } else {
      ToggleSwitch.id += 1;
    }
    this.id = ToggleSwitch.id;
  }

  render() {
    return (
        <input id={`prefix-${this.id}`} />
    );
  }
}
OZZIE
la source
-1

Autre moyen simple avec dactylographié:

static componentsCounter = 0;

componentDidMount() {
  this.setState({ id: 'input-' + Input.componentsCounter++ });
}
Lucas Moyano Angelini
la source
2
Ceci est possible sans TypeScript
ChrisBrownie55
-1

Je crée un module générateur uniqueId (Typescript):

const uniqueId = ((): ((prefix: string) => string) => {
  let counter = 0;
  return (prefix: string): string => `${prefix}${++counter}`;
})();

export default uniqueId;

Et utilisez le module supérieur pour générer des identifiants uniques:

import React, { FC, ReactElement } from 'react'
import uniqueId from '../../modules/uniqueId';

const Component: FC = (): ReactElement => {
  const [inputId] = useState(uniqueId('input-'));
  return (
    <label htmlFor={inputId}>
      <span>text</span>
      <input id={inputId} type="text" />
    </label>
  );
};     
Masih Jahangiri
la source
-3

N'utilisez pas du tout d'ID si vous n'en avez pas besoin, enveloppez plutôt l'entrée dans une étiquette comme celle-ci:

<label>
   My Label
   <input type="text"/>
</label>

Vous n'aurez alors plus à vous soucier des identifiants uniques.

Mike Desjardins
la source
2
Bien que cela soit pris en charge par HTML5, il est déconseillé pour l'accessibilité: "Même dans de tels cas, il est cependant recommandé de définir l'attribut for car certaines technologies d'assistance ne comprennent pas les relations implicites entre les étiquettes et les widgets." - de developer.mozilla.org/en-US/docs/Learn/HTML/Forms
...
1
C'est la voie recommandée par l'équipe React selon les documents trouvés sur reactjs.org/docs/forms.html
Blake Plumb
1
L'équipe @BlakePlumb React a également une section de formulaires accessibles: reactjs.org/docs/accessibility.html#accessible-forms
Vic