204

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
  • 3,096
  • 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

15 Answers15

128

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
  • 18,092
  • 10
  • 50
  • 41
  • 11
    Or you could also put it into the constructor. – John Weisz Oct 21 '16 at 10:46
  • componentWillMount is deprecated since React 16.3.0, use UNSAFE_componentWillMount instead, see https://reactjs.org/docs/react-component.html#unsafe_componentwillmount – lokers Jan 03 '19 at 13:13
  • 2
    Can anyone suggest how this should be done with the new Hooks in React 16.8? – Aximili Mar 06 '19 at 00:12
  • 13
    As you're not tracking the value of the id, you can also use `const {current: id} = useRef(_uniqueId('prefix-'))` – forivall Oct 10 '19 at 00:31
  • 4
    What's the difference with using useRef instead of use State? – XPD Dec 05 '19 at 02:04
  • Why not use `const [randomId] = useState(Math.random().toString(36).substr(2));`? – igo Apr 18 '20 at 17:58
  • 5
    Don't use state for something that is not due to change (invariant) for a given component. It's wrong conceptually. – Tomek Sep 22 '21 at 09:39
  • I've added additional context to @forivall 's comment: https://stackoverflow.com/a/70039497/3673659 – Advena Nov 19 '21 at 18:42
  • Can we just put it outside the component? – Chang Yea Moon Apr 26 '22 at 17:08
  • @ChangYeaMoon I *thought* we can put it outside, but if you are using server side rendering like NextJS, it'll generate different id when on server and on client and you'll get console msg `Warning: Prop id did not match.`. – Shawn Mar 07 '23 at 20:50
96

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
  • 3,096
  • 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
93

Update React 18

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

const id = useId();

Hook API docs: https://react.dev/reference/react/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"/>
    </>
  );
}
Neil Gaetano Lindberg
  • 2,488
  • 26
  • 23
t_dom93
  • 10,226
  • 1
  • 52
  • 38
  • 1
    Please note that this is not a valid selector. If you need a valid selector, this is not the solution you need, and honestly, I don't know why they would make an ID hook that is not creating a valid selector... To prevent document.getElementById? it can, in rare cases be necessary – ii iml0sto1 Nov 17 '22 at 15:08
  • 2
    **Caution** - As @iiiml0sto1 mentioned, [`useId`](https://reactjs.org/docs/hooks-reference.html#useid) creates strings surrounded by colons (e.g. `:r5:`) which `querySelector` does not support. Colons have to be [escaped in when using a `querySelector`](https://stackoverflow.com/a/75178117/3673659). The [`useId` hook allows for a prefix](https://beta.reactjs.org/reference/react/useId#specifying-a-shared-prefix-for-all-generated-ids), but this will only prepend the string with value, while colons are still applied. – Advena Jan 25 '23 at 13:33
  • 1
    It clearly stated "Do not call useId to generate keys in a list. Keys should be generated from your data." here. https://beta.reactjs.org/reference/react/useId#usage – Zoedia Jan 31 '23 at 06:57
35

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,720
  • 1
  • 21
  • 29
  • 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
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
  • 20,072
  • 5
  • 36
  • 59
Stuart Ervine
  • 1,024
  • 13
  • 15
5

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.

Advena
  • 1,664
  • 2
  • 24
  • 45
4

A version without Lodash using hooks:

function useUniqueId() {
  const [id] = useState(() => `component-${Math.random().toString(16).slice(2)}`)
  return id
}
Joonas
  • 892
  • 6
  • 18
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,120
  • 13
  • 20
2

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,811
  • 3
  • 38
  • 57
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
  • 9,489
  • 3
  • 45
  • 51
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,928
  • 1
  • 28
  • 43
  • 2
    +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
-1

You can use library 'nanoid' like this

import { nanoid } from 'nanoid';

const id = nanoid();

if you need to generate a value of a specific length - pass the corresponding number to the function argument

const id = nanoid(17); //TFWI1r8HPqOLBW0vj

If you use @reduxjs/toolkit in your project you can import this function directly from there:

import { nanoid } from '@reduxjs/toolkit;
-1

React < 18

IMO the simpler solution to generate a unique ID for version of React < 18 is to use useState in combination with Date.getTime().

import { useState } from 'react'

function UniqueComponent = (props) => {

  const id = useState(new Date().getTime().toString()); 

  return (
    <>
      <label>
        Password:
        <input
          type="password"
          aria-describedby={id}
        />
      </label>
      <p id={id}>
        The password should contain at least 18 characters
      </p>
    </>
  );
}

React 18

React 18's useId hook does the same, generating unique IDs on render. It ends up being just a little bit shorter.

Docs: https://react.dev/reference/react/useId

Example;

import { useId } from 'react'

function UniqueComponent = (props) => {

  const id = useId();

  return (
    <>
      <label>
        Password:
        <input
          type="password"
          aria-describedby={id}
        />
      </label>
      <p id={id}>
        The password should contain at least 18 characters
      </p>
    </>
  );
}
Alain1405
  • 2,259
  • 2
  • 21
  • 40
-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
  • 6,609
  • 7
  • 55
  • 59
-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
  • 450
  • 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