New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unique component identifier #1137

Closed
andreypopp opened this Issue Feb 20, 2014 · 50 comments

Comments

Projects
None yet
@andreypopp
Contributor

andreypopp commented Feb 20, 2014

Both react-async and react-multiplayer need components to have unique identifiers.

In case of react-async — to maintain a mapping from a component to its state and make it serialisable (to send it over the wire, ES6 Map with components as keys can't be used), in case of react-multiplayer — to have an unique URL per component instance.

Currently both of the libraries use this._rootNodeID + ',' + this._mountDepth. Should this be added to a public API — getComponentID() or something?

@syranide

This comment has been minimized.

Contributor

syranide commented Feb 20, 2014

this._rootNodeID + ',' + this._mountDepth is redundant even, this._rootNodeID is enough. However, the unique ID is an implementation detail AFAIK, it's likely that it will be removed in the future if/when we ever let go of innerHTML. Also, it's possible that a concatenated ID won't be available soon, but each node will only hold it's own relative ID and possibly a monotonic unique ID for innerHTML (again, implementation detail).

Also this._rootNodeID is only unique at a single point in time, it's not unique over time. I'm unsure why this can't be solved for example via a Mixin that creates a unique ID for the component by just incrementing a global counter.

@sophiebits

This comment has been minimized.

Member

sophiebits commented Feb 20, 2014

@syranide _rootNodeID can be shared by multiple composite components, correct?

This seems like something that's fragile -- for cases where you only need a unique ID on one client but don't need it to be consistent across clients, you can make your own autoincrementing counter. For cases where you want the IDs to be consistent across clients, I don't think you can rely on this._rootNodeID because rendering components in a different order (or with server rendering, on different servers) will cause the node IDs to be different -- when you need this I think you really just want to force the person using a mixin to specify a unique key.

@syranide

This comment has been minimized.

Contributor

syranide commented Feb 20, 2014

@spicyj _rootNodeID must be unique at any point in time, but it only describes the hiearchy of indices and keys, so once a component is removed, the _rootNodeID is likely to be immediately assumed by another component taking it's place.

Anyway, @andreypopp messages me in the chat and it seemed like the Mixin approach was a good solution.

@sophiebits

This comment has been minimized.

Member

sophiebits commented Feb 20, 2014

@syranide I mean if you have two composite components nested with no DOM node in between then they share a rootNodeID:

var markup = this._renderedComponent.mountComponent(
rootID,
transaction,
mountDepth + 1
);

@syranide

This comment has been minimized.

Contributor

syranide commented Feb 20, 2014

@spicyj Ah right, right, _rootNodeID emphasis on Node as in DOMNode. Excellent point, they're only unique in the DOM (loosely speaking).

@syranide

This comment has been minimized.

Contributor

syranide commented May 18, 2014

@spicyj Can close this (this was solved on IRC btw).

@andreypopp andreypopp closed this May 18, 2014

@laser

This comment has been minimized.

laser commented Apr 23, 2015

@syranide @spicyj - For those of us who weren't on IRC - what was the resolution?

@syranide

This comment has been minimized.

Contributor

syranide commented Apr 23, 2015

@laser IIRC simply that it was there were better ways to approach the problem that doesn't rely on internals. I have a faint memory of him simply generating a unique ID for each component on mount and using that instead, but I could be way off.

@laser

This comment has been minimized.

laser commented Apr 23, 2015

@syranide Ah, okay. Thanks for clarifying.

My team is trying to come up with an approach for including a view-specific token with our actions such that errors (say, failing server-side validation) can be linked back to originating views w/out introducing granular, view-specific stores. The approach was suggested by @jingc here. I'd love to be able to rely on a unique id set by React instead of having to generate something myself, if possible.

Take care,

Erin

@sophiebits

This comment has been minimized.

Member

sophiebits commented Apr 23, 2015

@laser Yeah, if all you need is a unique ID for each component then you can make a counter yourself and increment it for each new component. (We try to avoid adding functionality to React if it can be easily replicated in component code.)

@laser

This comment has been minimized.

laser commented Apr 23, 2015

@spicyj Cool, makes sense.

Without thinking too hard about it, I could imagine something like:

class BaseComponent extends React.Component {
  constructor(props) {
    super(props);
    this.componentId = SomeLib.createComponentId();
  }
}
@lemiii

This comment has been minimized.

lemiii commented May 8, 2015

I don't necessarily like what i'm doing since it's hacky and has a chance of collisions when you have multiple instances on the same page, but I set the state of the component with a random id, and then use that.

getInitalState: function() {
  return { 
    id: Math.floor(Math.random() * 0xFFFF) 
  }
},

getId: function(name) {
  return name + this.state.id;
},

render: function() {
  return (
    <div>
      <input type="checkbox" id={this.id('agreeCheckbox')} />
      <label htmlFor={this.id('agreeCheckbox')}>I agree</label>
    </div>
  );
}
@davidgilbertson

This comment has been minimized.

Contributor

davidgilbertson commented Nov 18, 2015

A simple counter and component-specific string works just fine for me.

import React, {Component} from 'react';

let count = 0;

class InputUi extends Component {
    constructor(props) {
        super(props);

        this.guid = 'input-ui-' + count++;
    }

    render() {
        return (
            <span>
                <label htmlFor={this.guid}>
                    {this.props.label}
                </label>

                <input
                    id={this.guid}
                    />
            </span>
        );
    }
}

export default InputUi;

Is there a scenario where this won't work?

@syranide

This comment has been minimized.

Contributor

syranide commented Nov 18, 2015

@davidgilbertson Server-rendering, it will work in some trivial setups but for everything beyond that will cause count to become out-of-sync between client and server and cause markup mismatch.

@davidgilbertson

This comment has been minimized.

Contributor

davidgilbertson commented Nov 18, 2015

I thought that might be the case, but I've got 49 components and a few hundred instances nested up to 6-7 levels deep which I would consider non-trivial.

And it works fine.

Is there a specific scenario that comes to mind where the initial client-side markup rendered by ReactDom.render() won't match the markup generated by ReactDOMServer.renderToString()?

(Webpack's hot swapping will make them out of sync but I don't care about that.)

@omerts

This comment has been minimized.

omerts commented Apr 13, 2016

React 15 has nulled many _rootNodeId, any other ideas? Generating ids isn't a good solution for us, since we need a unique id for 3rd party components as well, and wrapping a 3rd party components is something we want to avoid.

@sophiebits

This comment has been minimized.

Member

sophiebits commented Apr 13, 2016

You could use a WeakMap and store an ID for each instance. _rootNodeID was never public API and we no longer needed a unique ID on composite components so we got rid of it. Depending on your use case, perhaps you can refactor your API so that the third-party components are wrapped and your wrapper can generate an ID.

@omerts

This comment has been minimized.

omerts commented Apr 14, 2016

@spicyj We are trying to make redux-devtools' time travelling feature to actually replay inner component states. So our use case, is being able to inject a state object into a component, only by ID, since instances get created/disposed during the time travel. So bottom line, our map consists of comoponentID -> componentState, and we use ID to locate the current component instance. This worked for us, until React 15 (we used some internals though).

@ricardosoeiro

This comment has been minimized.

ricardosoeiro commented Apr 17, 2016

We were also relying on _rootNodeID as an identifier to cache component state on componentWillUnmount, so that we can restore it for all components when the user presses the back button, on componentWillMount.

This is an important use case for us and this ID was a perfect fit. We knew we were taking a risk using internals, but still... is there any reliable alternative? We thought about generating some kind of component xpath by traversing the hierarchy, but it doesn't seem very performant to do for several components.

@gaearon

This comment has been minimized.

Member

gaearon commented Apr 17, 2016

@ricardosoeiro The approach you describe is very fragile and not really supported. Have you looked into using something like Flux or Redux instead? They support exactly this use case, but in a different and more explicit way.

@omerts

This comment has been minimized.

omerts commented Apr 17, 2016

@gaearon Do flux/redux support injecting inner component state? Thats our need, and i think @ricardosoeiro's too. We have solved our problem by travesing up the tree and building an id from the type of elements up the tree. I have the code at work, and ill post it tomorrow.

@gaearon

This comment has been minimized.

Member

gaearon commented Apr 17, 2016

@omerts No, they are meant as external state containers. Is there any reason you can’t convert your components to use external state rather than try to inject local state into them?

@omerts

This comment has been minimized.

omerts commented Apr 17, 2016

@gaearon mainly bcs of 3rd party controls

@gaearon

This comment has been minimized.

Member

gaearon commented Apr 17, 2016

@omerts Usually you don’t want to depend on third-party components that don’t offer a “controlled” API like value + onChange props. Is this definitely not the case for components you rely on?

@ricardosoeiro

This comment has been minimized.

ricardosoeiro commented Apr 17, 2016

@gaearon: why would we want to convert external components and then maintain them? This is a generic problem that calls for a generic approach... I'm not saying this should be supported out of the box, but I've seen this use case pop up several times, so it deserves at least a fair discussion :)

@ricardosoeiro

This comment has been minimized.

ricardosoeiro commented Apr 17, 2016

@omerts does your approach rely on other internals to traverse the component tree? I'm curious to see your code

@gaearon

This comment has been minimized.

Member

gaearon commented Apr 17, 2016

I'm not saying this should be supported out of the box, but I've seen this use case pop up several times, so it deserves at least a fair discussion :)

I agree, and this is being discussed here: #4595.

I’m just saying that, until something like this is supported, it is better to rely on proven solutions that work well (even if they require components to be controlled) rather than on internals that break with every release, and are likely to break completely after some work on incremental reconciler (#6170) or bundling React as a flat file (#6351).

@ricardosoeiro

This comment has been minimized.

ricardosoeiro commented Apr 18, 2016

An api to obtain an opaque identifier, as suggested in #3932, would solve this use case perfectly...

@omerts

This comment has been minimized.

omerts commented Apr 18, 2016

@gaearon, many 3rd pary components like react-toolbox have inner states for visual component state etc. Using the redux-devtools (thanks for that by the way :)), we are time travelling, and want to see the visual components' inner states change as well.

@ricardosoeiro, yes it does relay on internals mainly to traverse the tree up to the parent node. It creates an ID by using the names/displayNames of the constructors up the tree. It does create quite a long id, and is not necessarily 100% unique (if you have the same exact component in the same exact hierarchy, with the same parents, it will return the same id), but it works for us (maybe you can play with it to get something better, or do some kind of hashing to shorten the id).

function _buildKey(component, accKey = '') {
  if (!component) {
    return accKey
  }

  let currentKey = accKey + ','

  if (typeof component._currentElement.type === 'function') {
    currentKey += component._currentElement.type.displayName || component._currentElement.type.name
  } else {
    currentKey += component._currentElement.type
  }

  return _buildKey(component._nativeParent, currentKey)
}

We are using the new ReactInstrumenation API to get internal instances on mount/unmount

import ReactInstrumentation from 'react/lib/ReactInstrumentation'

const eventHandlers = {  
  onMountComponent(internalInstance) {  
  },
  onUnmountComponent(internalInstance) {   
  }
}

ReactInstrumentation.debugTool.addDevtool(eventHandlers)

We also agree, a unique, persistent, component ID would make things much easier, and shouldn't be too hard to implement as part of core React (I don't mind working on a pull request for it, if there isn't any objection from Facebook's side on having a unique component ID).

@coryhouse

This comment has been minimized.

Contributor

coryhouse commented Sep 15, 2016

I'd love to see a deterministic, unique component identifier added to React. Our QA team needs consistent IDs as hooks for automated UI testing. So we're currently adding IDs everywhere manually. I suspect we're not alone. @omerts solution looks promising, but a built in solution would likely perform better and more reliably.

@jwietelmann

This comment has been minimized.

jwietelmann commented Oct 4, 2016

I would like to throw in on this to lobby for a built-in solution. The roll-your-own counter solution, which I have used for years, requires a manual per-request reset on the server side in order to make isomorphic apps work properly. It feels completely acceptable in the context of writing a full app, if a bit confusing the first time you encounter the issue. But it becomes a huge pain when writing a DOM component library.

Consider the following: http://foundation.zurb.com/sites/docs/forms.html#checkboxes-and-radio-buttons. Now consider that I want to write a component package for NPM that encapsulates this into a nicer API. Here are some of my awful choices:

  • Bottle it up with a counter and live with the fact that my lib won't work in an isomorphic app (unacceptable)
  • Make end-users bring their own counter or unique ID and provide them to every component as props (unnecessarily verbose, places more requirements on composing library components into additional reusable ones)
  • Force the end-user of your components into a specific fluxish lib (just to get unique DOM IDs in a simple UI library? not worth it)

There's one halfway decent option that I can come up with, which I still don't love: Create a package that includes a UniqueIDProvider component whose descendants receive the counter function via context. This could be fairly elegant if you could install it via NPM with some convenience functions for decorating components to receive the context a la react-redux.

But I still think it's a little bit absurd not to include out-of-the-box support for something that comes up this frequently in the DOM.

@kromit

This comment has been minimized.

kromit commented Oct 24, 2016

2 years after the issue were closed there obviously still demand for a solution and discussion about it. I do not see any reason why the issue should not be reopened.

@sosz

This comment has been minimized.

sosz commented Feb 11, 2017

Please reopen this issue. It can be easily implemented, and is a mandatory feature.

@veob

This comment has been minimized.

veob commented Feb 14, 2017

If one uses underscore.js, they can use .uniqueId method. Works both in node and browser.

@coryhouse

This comment has been minimized.

Contributor

coryhouse commented Feb 14, 2017

@veob That's not deterministic. A deterministic id requires considering the entire component tree to assure it only changes when markup changes.

@austinhinderer

This comment has been minimized.

austinhinderer commented Mar 1, 2017

Jumping in as another user who is deeply interested in this feature. Not every environment allows you to isolate your testing to NodeJS/React for integration testing only. These deterministic, unique IDs are extremely important for building larger applications in an enterprise setting.

@klaygomes

This comment has been minimized.

klaygomes commented Mar 16, 2017

3 years and nothing yet? What a shame!

@jpfiorilla

This comment has been minimized.

jpfiorilla commented Jun 30, 2017

I used this._reactInternalInstance._debugID for unique classNames for a fancy scrollmagic/gsap effect and when i deployed the whole site shat itself lol

@OutThisLife

This comment has been minimized.

OutThisLife commented Jul 7, 2017

+1

@sfrieson

This comment has been minimized.

Contributor

sfrieson commented Jul 12, 2017

I have a solution that's working pretty well so far. I've been generating sequential ids with a simple utility, and living with the checksums for the time being. This is inspired by what @jwietelmann said above about resetting on a per-request basis but is maybe worth saying again. As he said, this not as much help if you're working on a component library, but works best when you're creating the entire app.

// utility to generate ids
let current = 0

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

export function resetIdCounter () { current = 0 }

And then in a root component I call the resetCounter function. On the server, this clears the id from memory each time there's a new server render. In the browser, it doesn't really reset anything, but that's fine.

Working well so far.

@mtrabelsi

This comment has been minimized.

mtrabelsi commented Sep 8, 2017

Actually it's not that complicated to get a unique ID:

  
constructor(props) {
      super(props);
      this.id = new Date().getTime();
  }

As for CSS selector needs the first letter to be a character you can prefix it with an 'id-' on the render method :

  render() {
      return (<div id={`id-${this.id}`}>
                           ...
              </div>);
  }

Works like a charm, don't wait for React - they have their own policy and list of future pending , maybe will never be implemented :)

@nhunzaker

This comment has been minimized.

Collaborator

nhunzaker commented Sep 8, 2017

@mtrabelsi That approach is dangerous because multiple components may be created in under a millisecond. The time resolution isn't high enough.

Still, the technique of assigning ids in the constructor is how I've approached this in the past. However I've used a uuid library for it, and usually prepend a prefix to make the id contextual:

constructor(props) {
    super(props);
    this.id = "thing-" + uuid()
}

This isn't perfect, and I still like to generate an ID based on props when I can, but it accomplishes the task if prop-based identifiers fail.

@jquense

This comment has been minimized.

Collaborator

jquense commented Sep 8, 2017

uuid's in constructors work fine if you don't need SSR, other tho it's likely the id's will mismatch between server and client :/

@omerts

This comment has been minimized.

omerts commented Sep 8, 2017

@mtabelsi @nhunzaker, both solutions will not create a persistent id, especially if a certain component is mounted/unmounted across renders (even moving between tabs might unmount a component), not to say between refreshes.

@nhunzaker

This comment has been minimized.

Collaborator

nhunzaker commented Sep 8, 2017

@omerts Correct. It definitely does not fix the issue.

I think this could be a really great feature. Unfortunately I don't really understand enough about Fiber to make it happen, but would happily assist anyone trying to figure this out.

@sophiebits

This comment has been minimized.

Member

sophiebits commented Sep 8, 2017

We should probably agree on whether we want this and how it should behave before considering implementations (unless it's just for a proof of concept).

@nhunzaker

This comment has been minimized.

Collaborator

nhunzaker commented Sep 8, 2017

@sophiebits Ah sorry, I shouldn't get ahead of myself! I'd love to figure out some resolution here.

@sebmarkbage

This comment has been minimized.

Member

sebmarkbage commented Sep 9, 2017

This issue isn't about IDs. It's about whether it's a good idea to preserve state across serialization and if the use cases for that in turn can't be modeled better. Don't get stuck on the ID thing. If serialization is a good idea maybe it's better we just build that in.

@vorillaz

This comment has been minimized.

vorillaz commented Apr 1, 2018

I prefer using HOCs where needed in order to easily manipulate nested components.

import React from 'react';
let idx = 0;
const uuid = () => idx++;

export default Wrapped => props => {
  const { id, ...rest } = props;
  const uniqueId = id ? id : `id-${uuid()}`;
  return <Wrapped {...rest} id={uniqueId} />;
};
@ruucm

This comment has been minimized.

ruucm commented Jul 2, 2018

For 2 Days research, I got a solution for this headache problem.

Here is my solution

Make Unique Id by props in React

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment