¿Cómo generar ID únicos para etiquetas de formulario en React?

128

Tengo elementos de formulario con labelsy quiero tener ID únicos para vincular labels a elementos con htmlForatributo. Algo como esto:

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

Solía ​​generar ID basados ​​en this._rootNodeIDpero no está disponible desde React 0.13. ¿Cuál es la mejor y / o más simple forma de hacerlo ahora?

Artem Sapegin
fuente
si está generando este elemento una y otra vez, supongo que en una declaración for ¿por qué no usar el iterador en él? Supongo que también podría llamar a una función que genera un guid único si un número de índice no es lo suficientemente bueno. stackoverflow.com/questions/105034/…
Chris Hawkes
1
Hay muchos elementos de forma diferentes en diferentes componentes y todos ellos deben tener ID únicos. La función para generar ID es lo que pensé y lo que haré si nadie sugiere una mejor solución.
Artem Sapegin
3
Puede almacenar un contador incremental "global" en alguna parte y usarlo. id = 'unique' + (++GLOBAL_ID);donde var GLOBAL_ID=0;?
WiredPrairie
1
Sé que llego muy, muy tarde a esta fiesta, pero otra alternativa es envolver la entrada en la etiqueta en lugar de usar ID, por ejemplo:<label>My label<input type="text"/></label>
Mike Desjardins

Respuestas:

85

Esta solución funciona bien para mí.

utils/newid.js:

let lastId = 0;

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

Y puedo usarlo así:

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

Pero no funcionará en aplicaciones isomorfas.

Añadido 17.08.2015 . En lugar de la función newId personalizada, puede usar uniqueId de lodash.

Actualizado 28.01.2016 . Es mejor generar ID en componentWillMount.

Artem Sapegin
fuente
3
Porque comenzará a generar ID desde el primero nuevamente en el navegador. Pero en realidad puede usar diferentes prefijos en el servidor y en el navegador.
Artem Sapegin
77
¡No hagas esto render! Crear la identificación encomponentWillMount
sarink
1
Ha creado un contenedor con estado, pero está descuidando el uso de setState y está violando la especificación para render. facebook.github.io/react/docs/component-specs.html . Sin embargo, debería ser bastante fácil de arreglar.
aij
3
Estoy usando uniqueId de lodash en el constructor y estoy usando setState para establecer el id. Funciona bien para mi aplicación solo cliente.
CrossProduct
1
componentWillMountestá en desuso, haga esto en el constructor en su lugar. Ver: reactjs.org/docs/react-component.html#unsafe_componentwillmount
Vic
78

La identificación debe colocarse dentro de componentWillMount (actualización para 2018) constructor, no render. Poniéndolo enrender volverá a generar nuevos identificadores innecesariamente.

Si está usando subrayado o lodash, hay una uniqueIdfunción, por lo que su código resultante debería ser algo como:

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

Actualización de ganchos 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>
  );
}
sarink
fuente
11
O también podría ponerlo en el constructor.
John Weisz
componentWillMount está en desuso desde React 16.3.0, use UNSAFE_componentWillMount en su lugar, consulte reactjs.org/docs/react-component.html#unsafe_componentwillmount
lokers
2
¿Alguien puede sugerir cómo se debe hacer esto con los nuevos Hooks en React 16.8?
Aximili
44
Como no está rastreando el valor de la identificación, también puede usarconst {current: id} = useRef(_uniqueId('prefix-'))
forivall
1
¿Cuál es la diferencia con usar useRef en lugar de usar State?
XPD
24

A partir del 04/04/2019, esto parece lograrse con los ganchos de reacción 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

Según tengo entendido, ignora el segundo elemento de la matriz en la desestructuración de la matriz que le permitiría actualizar id, y ahora tiene un valor que no se actualizará nuevamente durante la vida útil del componente.

El valor de idserá myprefix-<n>donde <n>es un valor entero incremental devuelto desde uniqueId. Si eso no es lo suficientemente exclusivo para ti, considera hacer tu propio me gusta

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

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

o echa un vistazo a la biblioteca que publiqué aquí: https://github.com/rpearce/simple-uniqueid . También hay cientos o miles de otras cosas de identificación únicas, pero lodash uniqueIdcon un prefijo debería ser suficiente para hacer el trabajo.


Actualizar 2019-07-10

Gracias a @Huong Hk por señalarme al estado inicial lento de los ganchos , cuya suma es que puedes pasarle una función useStateque solo se ejecutará en el montaje inicial.

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

// after
const [ id ] = useState(() => uniqueId('myprefix-'))
rpearce
fuente
1
Tengo los mismos problemas con la representación del servidor, como muchos otros métodos, mencionados en esta página: el componente se volverá a mostrar con una nueva ID en el navegador.
Artem Sapegin
@ArtemSapegin: hubo un problema ( github.com/facebook/react/issues/1137 ) en el proyecto React que discutía tener alguna forma de que los componentes tengan identificadores únicos, pero no creo que haya surgido nada. ¿Qué importancia tiene que las ID generadas sean las mismas entre el servidor y el cliente? Creo que para un <input />, lo que importa es que los atributos htmlFory iddeben estar unidos sin importar cuáles sean los valores.
rpearce
Es importante evitar actualizaciones DOM innecesarias, que causarán nuevas ID.
Artem Sapegin
66
Es mejor si proporciona una función como initialState# 1 en const [ id ] = useState(() => uniqueId('myprefix-'))lugar del resultado de una función # 2 const [ id ] = useState(uniqueId('myprefix-')) El estado: idde 2 formas anteriores no son diferentes. Pero lo diferente se uniqueId('myprefix-')ejecutará una vez (# 1) en lugar de cada re-renderizado (# 2). Ver: Estado inicial diferido : reactjs.org/docs/hooks-reference.html#lazy-initial-state ¿Cómo crear objetos caros perezosamente ?: reactjs.org/docs/…
Huong Nguyen
1
@HuongHk eso es increíble; No lo sabia! Actualizaré mi respuesta
rpearce
4

Podría usar una biblioteca como node-uuid para asegurarse de obtener identificadores únicos.

Instalar usando:

npm install node-uuid --save

Luego, en su componente de reacción, agregue lo siguiente:

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
fuente
2
La respuesta parece haber sido actualizada para cumplir con la especificación
Jonas Berlin
2
Esto no funciona en aplicaciones isomorfas, ya que la identificación generada en el servidor es diferente de la identificación generada en el cliente.
Daniel T.
2
Pero se afirma como parte de la respuesta, lo cual es muy engañoso
Tom McKenzie el
1
Sí, -1 por usar identificadores UNIVERSALMENTE únicos, es un martillo de tamaño universal para una uña de tamaño mundial.
Jon z
1

Espero que esto sea útil para cualquiera que esté buscando una solución universal / isomórfica, ya que el problema de la suma de verificación es lo que me llevó aquí en primer lugar.

Como se dijo anteriormente, he creado una utilidad simple para crear secuencialmente una nueva identificación. Como las ID siguen aumentando en el servidor y comienzan de nuevo desde 0 en el cliente, decidí restablecer el incremento cada vez que se inicia el SSR.

// utility to generate ids
let current = 0

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

export function resetIdCounter () { current = 0 }

Y luego, en el constructor del componente raíz o componentWillMount, llame al reinicio. Esto esencialmente restablece el alcance de JS para el servidor en cada render del servidor. En el cliente no tiene (y no debería) tener ningún efecto.

tenor528
fuente
aún podría tener conflictos de identificación si los clientes comienzan a nombrar entradas desde 0 nuevamente.
Tomasz Mularczyk
@Tomasz desea que el cliente vuelva a comenzar desde el formulario 0 para que las sumas de verificación coincidan.
tenor528
0

Para los usos habituales de labely input, es más fácil ajustar la entrada en una etiqueta como esta:

import React from 'react'

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

También hace posible que en las casillas de verificación / botones de radio se aplique el relleno al elemento raíz y aún así obtener comentarios del clic en la entrada.

Developia
fuente
1
+1 para facilitar y útil en algunos casos, -1 no utilizable con select, por ejemplo , etiquetas múltiples en diferentes posiciones, componentes de interfaz de usuario desacoplados, etc., también se recomienda usar identificadores a11y: en general, las etiquetas explícitas son mejor compatibles con la tecnología de asistencia, w3. org / WAI / tutoriales / formularios / etiquetas / ...
Michael B.
-1

Encontré una solución fácil como esta:

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
fuente
-1

Otra forma simple con mecanografiado:

static componentsCounter = 0;

componentDidMount() {
  this.setState({ id: 'input-' + Input.componentsCounter++ });
}
Lucas Moyano Angelini
fuente
2
Esto es posible sin TypeScript
ChrisBrownie55
-1

Creo un módulo generador de ID único (Typecript):

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

export default uniqueId;

Y use el módulo superior para generar identificadores únicos:

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
fuente
-3

No use ID en absoluto si no lo necesita, en su lugar, ajuste la entrada en una etiqueta como esta:

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

Entonces no tendrá que preocuparse por identificaciones únicas.

Mike Desjardins
fuente
2
Aunque esto es compatible con HTML5, se desaconseja la accesibilidad: "Sin embargo, incluso en tales casos, se considera una mejor práctica establecer el atributo for porque algunas tecnologías de asistencia no entienden las relaciones implícitas entre etiquetas y widgets". - desde developer.mozilla.org/en-US/docs/Learn/HTML/Forms/…
GuyPaddock
1
Esta es la forma recomendada por el equipo React según los documentos que se encuentran en reactjs.org/docs/forms.html
Blake Plumb
1
El equipo de @BlakePlumb React también tiene una sección de formularios accesibles: reactjs.org/docs/accessibility.html#accessible-forms
Vic