Jak wygenerować unikalne identyfikatory dla etykiet formularzy w React?

128

Mam elementy formularza z labelsi chcę mieć unikalne identyfikatory, aby połączyć labelje z elementami z htmlForatrybutem. Coś takiego:

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

Kiedyś generowałem identyfikatory na podstawie, this._rootNodeIDale jest niedostępne od React 0.13. Jaki jest najlepszy i / lub najprostszy sposób, aby to zrobić teraz?

Artem Sapegin
źródło
jeśli generujesz ten element w kółko, zakładam w instrukcji for, dlaczego nie użyć na nim iteratora? Przypuszczam, że możesz również wywołać funkcję, która generuje unikalny identyfikator guid, jeśli numer indeksu nie jest wystarczająco dobry. stackoverflow.com/questions/105034/…
Chris Hawkes
1
Istnieje wiele różnych elementów formularza w różnych komponentach i wszystkie powinny mieć unikalne identyfikatory. Funkcja generowania identyfikatorów jest tym, o czym myślałem i co zrobię, jeśli nikt nie zaproponuje lepszego rozwiązania.
Artem Sapegin
3
Możesz gdzieś przechowywać „globalny” licznik rosnący i używać go. id = 'unique' + (++GLOBAL_ID);gdzie var GLOBAL_ID=0;?
WiredPrairie
1
Wiem, że jestem bardzo, bardzo spóźniony na tę imprezę, ale inną alternatywą jest zawijanie danych wejściowych w etykiecie zamiast używania identyfikatorów, np .:<label>My label<input type="text"/></label>
Mike Desjardins

Odpowiedzi:

85

To rozwiązanie działa dobrze dla mnie.

utils/newid.js:

let lastId = 0;

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

I mogę tego użyć w ten sposób:

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

Ale nie będzie działać w aplikacjach izomorficznych.

Dodano 17.08.2015 . Zamiast niestandardowej funkcji newId możesz użyć uniqueId z lodash.

Zaktualizowano 28.01.2016 . Lepiej jest wygenerować ID w componentWillMount.

Artem Sapegin
źródło
3
Ponieważ zacznie ponownie generować identyfikatory od pierwszego w przeglądarce. Ale w rzeczywistości możesz użyć różnych prefiksów na serwerze i w przeglądarce.
Artem Sapegin
7
Nie rób tego render! Utwórz identyfikator wcomponentWillMount
sarink
1
Utworzyłeś kontener stanowy, ale zaniedbujesz użycie setState i naruszasz specyfikację dla render. facebook.github.io/react/docs/component-specs.html . Jednak powinno to być dość łatwe do naprawienia.
aij
3
Używam uniqueId z lodash w konstruktorze i używam setState, aby ustawić identyfikator. Działa dobrze w przypadku aplikacji tylko dla mojego klienta.
CrossProduct
1
componentWillMountjest przestarzałe, zamiast tego zrób to w konstruktorze. Zobacz: Reactjs.org/docs/react-component.html#unsafe_componentwillmount
Vic,
78

Identyfikator należy umieścić w module componentWillMount (aktualizacja na 2018 r.) constructor, A nie render. Wprowadzenie go renderspowoduje niepotrzebne ponowne wygenerowanie nowych identyfikatorów.

Jeśli używasz podkreślenia lub lodash, istnieje uniqueIdfunkcja, więc wynikowy kod powinien wyglądać mniej więcej tak:

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

Aktualizacja haków 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
źródło
11
Możesz też umieścić go w konstruktorze.
John Weisz
KomponentWillMount jest przestarzały od React 16.3.0, zamiast tego użyj UNSAFE_componentWillMount, patrz Reactjs.org/docs/react-component.html#unsafe_componentwillmount
lokers
2
Czy ktoś może zasugerować, jak należy to zrobić z nowymi hookami w Reakcie 16.8?
Aximili
4
Ponieważ nie const {current: id} = useRef(_uniqueId('prefix-'))
śledzisz
1
Jaka jest różnica w używaniu useRef zamiast use State?
XPD
24

Po 04.04.2019 wydaje się, że można to osiągnąć za pomocą haków React 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

Jak rozumiem, ignorujesz drugi element tablicy w procesie destrukturyzacji tablicy, który pozwoliłby na aktualizację id, a teraz masz wartość, która nie zostanie ponownie zaktualizowana przez cały okres użytkowania komponentu.

Wartość idwill to myprefix-<n>gdzie <n>jest zwracana przyrostowa wartość całkowita uniqueId. Jeśli to nie jest dla Ciebie wystarczająco wyjątkowe, rozważ stworzenie własnego lajka

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

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

lub sprawdź bibliotekę, którą opublikowałem wraz z tym tutaj: https://github.com/rpearce/simple-uniqueid . Istnieją również setki lub tysiące innych unikalnych identyfikatorów, ale lodash uniqueIdz prefiksem powinien wystarczyć, aby wykonać zadanie.


Aktualizacja 2019-07-10

Dzięki @Huong Hk za wskazanie mi leniwego stanu początkowego hooków , którego sumą jest to, że możesz przekazać funkcję, useStatektóra zostanie uruchomiona tylko na początkowym montowaniu.

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

// after
const [ id ] = useState(() => uniqueId('myprefix-'))
rpearce
źródło
1
Mam te same problemy z renderowaniem na serwerze, co wiele innych metod wymienionych na tej stronie: komponent wyrenderuje się z nowym ID w przeglądarce.
Artem Sapegin
@ArtemSapegin: wystąpił problem ( github.com/facebook/react/issues/1137 ) w projekcie React omawiający sposób, aby komponenty miały unikalne identyfikatory, ale nie sądzę, aby cokolwiek z tego wyszło. Jakie znaczenie ma to, że generowane identyfikatory są takie same między serwerem a klientem? Wydaje mi się, że w przypadku an <input />liczy się to, że atrybuty htmlFori idpowinny być ze sobą powiązane, niezależnie od wartości.
rpearce
Ważne jest, aby unikać niepotrzebnych aktualizacji DOM, które spowodują nowe identyfikatory.
Artem Sapegin
6
Lepiej, jeśli podasz funkcję jako initialState# 1 const [ id ] = useState(() => uniqueId('myprefix-'))zamiast jako wynik funkcji # 2. const [ id ] = useState(uniqueId('myprefix-')) Stany: iddwóch powyższych sposobów nie różnią się. Ale inne uniqueId('myprefix-')zostanie wykonane raz (nr 1) zamiast każdego ponownego renderowania (nr 2). Zobacz: Leniwy stan początkowy: awarejs.org/docs/hooks-reference.html#lazy-initial-state Jak leniwie tworzyć drogie obiekty ?: awarejs.org/docs/…
Huong Nguyen
1
@HuongHk to niesamowite; Nie wiedziałem! Zaktualizuję odpowiedź
rpearce
4

Możesz użyć do tego biblioteki, takiej jak node-uuid, aby upewnić się, że otrzymujesz unikalne identyfikatory.

Zainstaluj za pomocą:

npm install node-uuid --save

Następnie w komponencie reaguj dodaj:

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
źródło
2
Wydaje się, że odpowiedź została zaktualizowana, aby była zgodna ze specyfikacją
Jonas Berlin
2
Nie działa to w aplikacjach izomorficznych, ponieważ identyfikator wygenerowany na serwerze jest inny niż identyfikator wygenerowany na kliencie.
Daniel T.
2
Ale jest to określone jako część odpowiedzi, która jest bardzo myląca
Tom McKenzie
1
Tak, -1 za używanie UNIWERSALNIE unikalnych identyfikatorów, to młotek wielkości wszechświata do gwoździa światowej wielkości.
Jon z
1

Mam nadzieję, że jest to pomocne dla każdego, kto szuka uniwersalnego / izomorficznego rozwiązania, ponieważ kwestia sumy kontrolnej doprowadziła mnie do tego w pierwszej kolejności.

Jak wspomniano powyżej, stworzyłem proste narzędzie do sekwencyjnego tworzenia nowego identyfikatora. Ponieważ identyfikatory stale rosną na serwerze i zaczynają od 0 w kliencie, zdecydowałem się zresetować przyrost przy każdym uruchomieniu SSR.

// utility to generate ids
let current = 0

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

export function resetIdCounter () { current = 0 }

Następnie w konstruktorze komponentu głównego lub componentWillMount wywołaj reset. To zasadniczo resetuje zakres JS dla serwera w każdym renderowaniu serwera. W kliencie nie ma to (i nie powinno) mieć żadnego efektu.

tenor528
źródło
nadal możesz mieć konflikty identyfikatorów, jeśli klienci ponownie zaczną nazywać dane wejściowe od 0.
Tomasz Mularczyk
@Tomasz chcesz, aby klient zaczął z powrotem od formularza 0, aby sumy kontrolne były zgodne.
tenor 528
0

W przypadku zwykłych zastosowań labeli inputpo prostu łatwiej jest umieścić dane wejściowe w takiej etykiecie:

import React from 'react'

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

Umożliwia również w polach wyboru / przyciskach radiowych stosowanie dopełnienia do elementu głównego i nadal otrzymywanie informacji zwrotnych o kliknięciu wejścia.

Developia
źródło
1
+1 dla łatwości i przydatne w niektórych przypadkach, -1 nie nadaje się do użycia np. Z selectwieloma etykietami na różnych pozycjach, odłączonymi komponentami interfejsu użytkownika itp., Zaleca się również używanie identyfikatorów a11y: Ogólnie rzecz biorąc, wyraźne etykiety są lepiej obsługiwane przez technologię wspomagającą, w3. org / WAI / tutorials / form / labels /…
Michael B.
-1

Znalazłem takie proste rozwiązanie:

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
źródło
-1

Inny prosty sposób z maszynopisem:

static componentsCounter = 0;

componentDidMount() {
  this.setState({ id: 'input-' + Input.componentsCounter++ });
}
Lucas Moyano Angelini
źródło
2
Jest to możliwe bez TypeScript
ChrisBrownie55
-1

Tworzę moduł generatora uniqueId (Typescript):

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

export default uniqueId;

I użyj górnego modułu do generowania unikalnych identyfikatorów:

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
źródło
-3

W ogóle nie używaj identyfikatorów, jeśli nie musisz, zamiast tego zawiń dane wejściowe etykietą taką jak ta:

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

Wtedy nie musisz martwić się o unikalne identyfikatory.

Mike Desjardins
źródło
2
Chociaż jest to obsługiwane przez HTML5, odradza się to ze względu na dostępność: „Jednak nawet w takich przypadkach za najlepszą praktykę uważa się ustawienie atrybutu for, ponieważ niektóre technologie pomocnicze nie rozumieją niejawnych relacji między etykietami i widżetami”. - from developer.mozilla.org/en-US/docs/Learn/HTML/Forms/…
GuyPaddock
1
To jest sposób zalecany przez zespół React zgodnie z dokumentacją znajdującą się pod adresem actjs.org/docs/forms.html
Blake Plumb,
1
Zespół @BlakePlumb React ma również dostępną sekcję formularzy: awarejs.org/docs/accessibility.html#accessible-forms
Vic