177

I have form elements with labels and I want to have unique IDs to link labels to elements with htmlFor attribute. Something like this:

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

I used to generate IDs based on this._rootNodeID but it’s unavailable since React 0.13. What is the best and/or simplest way to do it now?

Artem Sapegin
  • 2,897
  • 2
  • 16
  • 20
  • if you're generating this element over and over again, I'm assuming in a for statement why not use the iterator on it? I suppose you could also call a function that generates a unique guid if an index number is not good enough. http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript – Chris Hawkes Apr 02 '15 at 19:28
  • 1
    There are many different form element in different components and all of them should have unique IDs. Function to generate IDs is what I thought about and what I’m going to do if nobody suggests better solution. – Artem Sapegin Apr 02 '15 at 19:41
  • 3
    You can store a "global" incrementing counter somewhere and use that. `id = 'unique' + (++GLOBAL_ID);` where `var GLOBAL_ID=0;`? – WiredPrairie Apr 02 '15 at 22:59
  • 2
    I know I'm very, very late to this party, but another alternative is to wrap the input in the label instead of using IDs, e.g.: `` – Mike Desjardins Sep 18 '18 at 20:24
  • For those who are using hooks, React 18 added `useId` hook for generating unique IDs, see on your example: https://stackoverflow.com/a/71681435/6774916 – t_dom93 Mar 30 '22 at 17:15

13 Answers13

117

The id should be placed inside of componentWillMount (update for 2018) constructor, not render. Putting it in render will re-generate new ids unnecessarily.

If you're using underscore or lodash, there is a uniqueId function, so your resulting code should be something like:

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

2019 Hooks update:

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>
  );
}
Kabir Sarin
  • 16,301
  • 8
  • 44
  • 38
94

This solutions works fine for me.

utils/newid.js:

let lastId = 0;

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

And I can use it like this:

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

But it won’t work in isomorphic apps.

Added 17.08.2015. Instead of custom newId function you can use uniqueId from lodash.

Updated 28.01.2016. It’s better to generate ID in componentWillMount.

Artem Sapegin
  • 2,897
  • 2
  • 16
  • 20
  • Could you explain why it won't work in isomorphic apps? – klaasman Aug 10 '15 at 08:07
  • 4
    Because it will start to generate IDs from the 1st again in browser. But actually you can use different prefixes on server and in browser. – Artem Sapegin Aug 10 '15 at 11:45
  • 1
    Have you found any solution for isomorphic apps? I get the checksum failure due to the id being different on client and server. – David Gilbertson Nov 18 '15 at 21:05
  • 8
    Don't do this in `render`! Create the id in `componentWillMount` – Kabir Sarin Jan 27 '16 at 20:36
  • 1
    You've made a stateful container, but are neglecting to use setState and are violating the spec for `render`. https://facebook.github.io/react/docs/component-specs.html . It should be pretty easy to fix though. – aij Feb 29 '16 at 23:36
  • 3
    I am using uniqueId from lodash in the constructor and using setState to set the id. Works well for my client only app. – CrossProduct Apr 12 '16 at 21:48
  • Would a solution for the isomorphic app be to set a global variable, sent from the server, that is equal to the last ID used. Then, the app's newid object could start from there? Or, like @Artem Sagepin said, just use a prefix. – adam-beck Jan 12 '17 at 18:58
  • would the id overflow at some point (if the application is not meant to be restarted)? – Vic Nov 30 '17 at 20:17
  • 1
    `componentWillMount` is deprecated, do this in constructor instead. See: https://reactjs.org/docs/react-component.html#unsafe_componentwillmount – Vic Jun 27 '18 at 16:04
  • I've created a package based on this idea https://www.npmjs.com/package/react-id-generator – Tomasz Mularczyk Jul 02 '19 at 12:21
31

Following up as of 2019-04-04, this seems to be able to be accomplished with the 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

As I understand it, you ignore the second array item in the array destructuring that would allow you to update id, and now you've got a value that won't be updated again for the life of the component.

The value of id will be myprefix-<n> where <n> is an incremental integer value returned from uniqueId. If that's not unique enough for you, consider making your own like

const uniqueId = (prefix = 'id-') =>
  prefix + Math.random().toString(16).slice(-4)

There are also hundreds or thousands of other unique ID things out there, but lodash's uniqueId with a prefix should be enough to get the job done.


Update 2019-07-10

Thanks to @Huong Hk for pointing me to hooks lazy initial state, the sum of which is that you can pass a function to useState that will only be run on the initial mount.

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

// after
const [ id ] = useState(() => uniqueId('myprefix-'))
rpearce
  • 1,634
  • 1
  • 21
  • 28
  • 1
    I has the same problems with server rendering, as many other methods, mentioned on this page: the component will rerender with a new ID in the browser. – Artem Sapegin Apr 05 '19 at 07:46
  • @ArtemSapegin: there was an issue (https://github.com/facebook/react/issues/1137) on the React project discussing having some way to have components have unique ids, but I don't think anything came of it. How significant is it that the generated IDs are the same between server and client? I would think that for an ``, what would matter is that the `htmlFor` and `id` attributes should be tied together no matter what the values are. – rpearce Apr 05 '19 at 23:08
  • It's significant to avoid unnecessary DOM updates, that new IDs will cause. – Artem Sapegin Apr 09 '19 at 06:52
  • @ArtemSapegin I don't believe that is the case? Using `useState` will only run the ID generator once, and all subsequent re-renders (while the component is mounted) should skip over that entirely and give you the same value. – rpearce May 08 '19 at 23:26
  • 8
    It's better if you provide a function as `initialState` #1 ```const [ id ] = useState(() => uniqueId('myprefix-'))``` instead of result of a function #2 ```const [ id ] = useState(uniqueId('myprefix-'))``` The state: `id` of 2 ways above are not be different. But the different is ```uniqueId('myprefix-')``` will be executed once (#1) instead of every re-rendered (#2). See: Lazy initial state: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state How to create expensive objects lazily?: https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily – Huong Nguyen Jul 09 '19 at 07:29
  • 1
    @HuongHk that's amazing; I didn't know! I'll update my answer – rpearce Jul 11 '19 at 02:00
17

Update React 18

React 18 has introduced a new hook which generates a unique ID:

const id = useId();

Hook API docs: https://reactjs.org/docs/hooks-reference.html#useid

From your example, you could call the hook inside a component:

import React, { useId } from 'react'

function TextField = (props) => {
  // generate unique ID
  const id = useId(); 

  return (
    <>
      <label htmlFor={id}>My label</label>
      <input id={id} type="text"/>
    </>
  );
}
Artem Sapegin
  • 2,897
  • 2
  • 16
  • 20
t_dom93
  • 7,622
  • 1
  • 42
  • 34
9

You could use a library such as node-uuid for this to make sure you get unique ids.

Install using:

npm install node-uuid --save

Then in your react component add the following:

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>
    );
  }   
}
ravibagul91
  • 18,551
  • 5
  • 33
  • 54
Stuart Ervine
  • 1,004
  • 13
  • 15
4

Extending @forivall's comment

If the whole goal is to link up a <label> and <input> elements and they don't depend on props, then instead of using auto generated unique id's, the most optimal and performant approach would be to use useRef.

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

Meaning, you can use useRef to mimic instance variables which is not recomputed on props changes. useRef is not only used to reference a DOM element.

Example using an external random ID generator (e.g. loadash)

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

function InputField = (props) => {
  const {current: fieldId} = useRef(uniqueId('prefix-'))
  return (
    <div>
      <input id={fieldId} type="checkbox" />
      <label htmlFor={fieldId}>label</label>
    </div>
  );
}

Example using a simple custom random ID generator

import React, { useRef } from 'react'

function InputField = (props) => {
  const {current: fieldId} = useRef("prefix-" + (Math.random().toString(36)+'00000000000000000').slice(2, 7))
  return (
    <div>
      <input id={fieldId} type="checkbox" />
      <label htmlFor={fieldId}>label</label>
    </div>
  );
}

Explanation:

The above random ID (Math.random().toString(36)+'00000000000000000').slice(2, 7) comes from this stackoverflow answer and will always guarantee 5 characters, compared to Math.random().toString(16).slice(-4) which may return empty strings.

Also, it's important to use a prefix where the prefix must start with a letter ([A-Za-z]) in order for it to be a valid HTML4 id attribute value.

Tanckom
  • 1,102
  • 2
  • 14
  • 35
2

Hopefully this is helpful to anyone coming looking for a universal/isomorphic solution, since the checksum issue is what led me here in the first place.

As said above, I've created a simple utility to sequentially create a new id. Since the IDs keep incrementing on the server, and start over from 0 in the client, I decided to reset the increment each the SSR starts.

// utility to generate ids
let current = 0

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

export function resetIdCounter () { current = 0 }

And then in the root component's constructor or componentWillMount, call the reset. This essentially resets the JS scope for the server in each server render. In the client it doesn't (and shouldn't) have any effect.

tenor528
  • 1,100
  • 13
  • 20
2

A version without Lodash using hooks:

function useUniqueId() {
  const [id] = useState(() => `component-${Math.random().toString(16).slice(2)}`)
  return id
}
Joonas
  • 597
  • 5
  • 14
1

I create a uniqueId generator module (Typescript):

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

export default uniqueId;

And use top module to generate unique ids:

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
  • 6,571
  • 2
  • 35
  • 36
0

For the usual usages of label and input, it's just easier to wrap input into a label like this:

import React from 'react'

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

It's also makes it possible in checkboxes/radiobuttons to apply padding to root element and still getting feedback of click on input.

Developia
  • 3,800
  • 1
  • 27
  • 41
  • 1
    +1 for easyness and useful for some cases, -1 not usable with e.g. `select`, multiple-labels on different positions, uncoupled ui components etc., also using ids is recommended a11y: Generally, explicit labels are better supported by assistive technology, https://www.w3.org/WAI/tutorials/forms/labels/#associating-labels-implicitly – Michael B. Jan 18 '20 at 22:59
0

The useId hook will replace the unstable useOpaqueIdentifier in an upcoming stable version of React (already available in the latest React alphas). It will generate stable ids during server rendering and hydration to avoid mismatches.

Lucky Soni
  • 6,636
  • 3
  • 35
  • 56
-2

I found an easy solution like this:

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
  • 5,351
  • 5
  • 48
  • 56
-4

Don't use IDs at all if you don't need to, instead wrap the input in a label like this:

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

Then you won't need to worry about unique IDs.

Mike Desjardins
  • 435
  • 4
  • 10
  • 3
    Although this is supported by HTML5, it's discouraged for accessibility: "Even in such cases however, it is considered best practice to set the for attribute because some assistive technologies do not understand implicit relationships between labels and widgets." -- from https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form#The_%3Clabel%3E_element – GuyPaddock Oct 01 '18 at 01:03
  • 1
    This is the way recommend by the React team according to the docs found at https://reactjs.org/docs/forms.html – Blake Plumb Oct 20 '18 at 04:49
  • 1
    @BlakePlumb React team also has an accessible forms section: https://reactjs.org/docs/accessibility.html#accessible-forms – Vic Jan 08 '19 at 20:11