Como gerar IDs exclusivos para rótulos de formulário no React?

128

Eu tenho elementos de formulário com labels e quero ter IDs exclusivos para vincular labels a elementos com htmlForatributo. Algo assim:

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

Eu costumava gerar IDs com base, this._rootNodeIDmas não está disponível desde o React 0.13. Qual é a melhor e / ou mais simples maneira de fazer isso agora?

Artem Sapegin
fonte
se você estiver gerando esse elemento repetidamente, estou assumindo que em uma declaração for, por que não usar o iterador? Suponho que você também possa chamar uma função que gere um guia exclusivo se um número de índice não for bom o suficiente. stackoverflow.com/questions/105034/…
Chris Hawkes
1
Existem muitos elementos de formulário diferentes em componentes diferentes e todos eles devem ter IDs exclusivos. A função para gerar IDs é o que pensei e o que vou fazer se ninguém sugerir uma solução melhor.
Artem Sapegin
3
Você pode armazenar um contador de incremento "global" em algum lugar e usá-lo. id = 'unique' + (++GLOBAL_ID);onde var GLOBAL_ID=0;?
WiredPrairie
1
Eu sei que sou muito, muito tarde para esta festa, mas outra alternativa é envolver a entrada no rótulo em vez de usar IDs, por exemplo:<label>My label<input type="text"/></label>
Mike Desjardins

Respostas:

85

Essas soluções funcionam bem para mim.

utils/newid.js:

let lastId = 0;

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

E eu posso usá-lo assim:

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

Mas não funcionará em aplicativos isomórficos.

Adicionado 17.08.2015 . Em vez da função newId personalizada, você pode usar uniqueId do lodash.

Atualizado 28.01.2016 . É melhor gerar um ID componentWillMount.

Artem Sapegin
fonte
3
Porque ele começará a gerar IDs a partir do 1º novamente no navegador. Mas, na verdade, você pode usar prefixos diferentes no servidor e no navegador.
Artem Sapegin
7
Não faça isso render! Crie o id emcomponentWillMount
sarink
1
Você criou um contêiner com estado, mas está deixando de usar setState e está violando as especificações render. facebook.github.io/react/docs/component-specs.html . Deve ser bem fácil de corrigir.
aij
3
Estou usando uniqueId da lodash no construtor e usando setState para definir o ID. Funciona bem para o meu aplicativo somente cliente.
usar o seguinte
1
componentWillMountestiver obsoleto, faça isso no construtor. Veja: reactjs.org/docs/react-component.html#unsafe_componentwillmount
Vic
78

O ID deve ser colocado dentro de componentWillMount (atualização para 2018) constructor, não render. Colocá-lo renderirá gerar novos IDs desnecessariamente.

Se você estiver usando sublinhado ou lodash, há uma uniqueIdfunção; portanto, o código resultante deve 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>
  );
}

Atualização de ganchos de 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
fonte
11
Ou você também pode colocá-lo no construtor.
21716 John Weisz
componentWillMount está obsoleto desde a React 16.3.0; em vez disso, use UNSAFE_componentWillMount, consulte reactjs.org/docs/react-component.html#unsafe_componentwillmount
lokers
2
Alguém pode sugerir como isso deve ser feito com os novos Hooks no React 16.8?
Aximili 6/03/19
4
Como você não está acompanhando o valor do id, você também pode usarconst {current: id} = useRef(_uniqueId('prefix-'))
forivall
1
Qual é a diferença com o uso de useRef em vez de usar State?
XPD
24

No seguimento de 04-04-2019, isso parece ser possível com o 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

Pelo que entendi, você ignora o segundo item da matriz na desestruturação da matriz que permitiria a atualização ide agora você tem um valor que não será atualizado novamente durante a vida útil do componente.

O valor de idserá myprefix-<n>onde <n>é um valor inteiro incremental retornado uniqueId. Se isso não for único o suficiente para você, considere fazer o seu próprio

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 confira a biblioteca que publiquei aqui: https://github.com/rpearce/simple-uniqueid . Existem também centenas ou milhares de outras coisas únicas de identificação por aí, mas os lodashs uniqueIdcom um prefixo devem ser suficientes para fazer o trabalho.


Atualização 2019-07-10

Agradeço ao @Huong Hk por me indicar um estado inicial lento , cuja soma é que você pode passar uma função para useStateque só seja executada na montagem inicial.

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

// after
const [ id ] = useState(() => uniqueId('myprefix-'))
rpearce
fonte
1
Tenho os mesmos problemas com a renderização do servidor, como muitos outros métodos mencionados nesta página: o componente será renderizado novamente com um novo ID no navegador.
Artem Sapegin
@ArtemSapegin: houve um problema ( github.com/facebook/react/issues/1137 ) no projeto React, discutindo ter alguma maneira de ter componentes com identificações únicas, mas acho que não aconteceu nada disso. Quão significativo é que os IDs gerados sejam os mesmos entre servidor e cliente? Eu acho que, para um <input />, o que importa é que os atributos htmlFore iddevem estar ligados, independentemente dos valores.
precisa saber é
É importante evitar atualizações DOM desnecessárias, que novos IDs causarão.
Artem Sapegin
6
É melhor se você fornecer uma função como initialState# 1 em const [ id ] = useState(() => uniqueId('myprefix-'))vez de resultado de uma função # 2 const [ id ] = useState(uniqueId('myprefix-')) O estado: idde 2 maneiras acima não serão diferentes. Mas o diferente é uniqueId('myprefix-')será executado uma vez (# 1) em vez de cada re-renderizado (# 2). Veja: preguiçoso estado inicial: reactjs.org/docs/hooks-reference.html#lazy-initial-state Como criar objetos caros preguiçosamente ?: reactjs.org/docs/...
Huong Nguyen
1
@HuongHk isso é incrível; Eu não sabia! Vou atualizar minha resposta
rpearce
4

Você pode usar uma biblioteca como o node-uuid para garantir que você obtenha IDs exclusivos.

Instale usando:

npm install node-uuid --save

Em seguida, no seu componente de reação, adicione o seguinte:

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
fonte
2
A resposta parece ter sido atualizada para estar em conformidade com as especificações
Jonas Berlin
2
Isso não funciona em aplicativos isomórficos, pois o ID gerado no servidor é diferente do ID gerado no cliente.
Daniel T.
2
Mas afirma-se como parte da resposta, que é muito enganador
Tom McKenzie
1
Sim, -1 por usar IDs UNIVERSALMENTE únicos, que é um martelo do tamanho de um universo para uma unha de tamanho mundial.
z
1

Espero que isso seja útil para quem procura uma solução universal / isomórfica, já que o problema da soma de verificação foi o que me levou aqui em primeiro lugar.

Como dito acima, criei um utilitário simples para criar sequencialmente um novo ID. Como os IDs continuam aumentando no servidor e iniciam novamente a partir de 0 no cliente, decidi redefinir o incremento a cada inicialização do SSR.

// utility to generate ids
let current = 0

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

export function resetIdCounter () { current = 0 }

E, em seguida, no construtor do componente raiz ou componentWillMount, chame a redefinição. Isso redefine essencialmente o escopo JS do servidor em cada renderização do servidor. No cliente, isso não (e não deve) ter nenhum efeito.

tenor528
fonte
você ainda pode ter conflitos de identificação se os clientes começarem a nomear entradas de 0 novamente.
Tomasz Mularczyk
@ Tomasz, você deseja que o cliente inicie novamente o formulário 0 para que as somas de verificação correspondam.
precisa saber é o seguinte
0

Para os usos usuais de labele input, é apenas mais fácil agrupar as entradas em um rótulo como este:

import React from 'react'

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

Também possibilita nas caixas de seleção / botões de opção aplicar preenchimento ao elemento raiz e ainda obter feedback do clique na entrada.

Developia
fonte
1
1 para easyness e útil para alguns casos, não utilizável -1 com, por exemplo select, múltiplos-etiquetas em posições diferentes, componentes ui desacoplados etc., também usando ids é recomendado A11y: Em geral, as etiquetas explícitas são melhor suportados pela tecnologia de apoio, w3. org / WAI / tutoriais / formulários / etiquetas /…
Michael B.
-1

Encontrei uma solução 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
fonte
-1

Outra maneira simples com o texto datilografado:

static componentsCounter = 0;

componentDidMount() {
  this.setState({ id: 'input-' + Input.componentsCounter++ });
}
Lucas Moyano Angelini
fonte
2
Isso é possível sem texto datilografado
ChrisBrownie55
-1

Eu crio um módulo gerador uniqueId (Typescript):

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

export default uniqueId;

E use o módulo superior para gerar IDs exclusivos:

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

Não use IDs, se você não precisar, em vez disso, envolva a entrada em um rótulo como este:

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

Então você não precisará se preocupar com IDs únicos.

Mike Desjardins
fonte
2
Embora isso seja suportado pelo HTML5, é desencorajado a acessibilidade: "Mesmo nesses casos, no entanto, é considerado uma prática recomendada definir o atributo for porque algumas tecnologias assistivas não entendem os relacionamentos implícitos entre rótulos e widgets". - from developer.mozilla.org/pt-BR/docs/Learn/HTML/Forms/…
GuyPaddock
1
Esta é a maneira recomendada pela equipe React de acordo com os documentos encontrados em reactjs.org/docs/forms.html
Blake Plumb
1
A equipe do @BlakePlumb React também possui uma seção de formulários acessíveis: reactjs.org/docs/accessibility.html#accessible-forms
Vic