-
Notifications
You must be signed in to change notification settings - Fork 47.2k
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
Comments
Also |
@syranide 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 |
@spicyj Anyway, @andreypopp messages me in the chat and it seemed like the Mixin approach was a good solution. |
@syranide I mean if you have two composite components nested with no DOM node in between then they share a rootNodeID: react/src/core/ReactCompositeComponent.js Lines 839 to 843 in 95edc39
|
@spicyj Ah right, right, |
@spicyj Can close this (this was solved on IRC btw). |
@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. |
@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 |
@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.) |
@spicyj Cool, makes sense. Without thinking too hard about it, I could imagine something like:
|
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>
);
} |
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? |
@davidgilbertson Server-rendering, it will work in some trivial setups but for everything beyond that will cause |
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 (Webpack's hot swapping will make them out of sync but I don't care about that.) |
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. |
You could use a WeakMap and store an ID for each instance. |
@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). |
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. |
@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. |
@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. |
@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? |
@gaearon mainly bcs of 3rd party controls |
@omerts Usually you don’t want to depend on third-party components that don’t offer a “controlled” API like |
@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 :) |
@omerts does your approach rely on other internals to traverse the component tree? I'm curious to see your code |
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). |
An api to obtain an opaque identifier, as suggested in #3932, would solve this use case perfectly... |
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. |
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:
There's one halfway decent option that I can come up with, which I still don't love: Create a package that includes a 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. |
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. |
Please reopen this issue. It can be easily implemented, and is a mandatory feature. |
If one uses underscore.js, they can use .uniqueId method. Works both in node and browser. |
@veob That's not deterministic. A deterministic id requires considering the entire component tree to assure it only changes when markup changes. |
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. |
3 years and nothing yet? What a shame! |
I used |
+1 |
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.
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. |
Actually it's not that complicated to get a unique ID:
As for CSS selector needs the first letter to be a character you can prefix it with an 'id-' on the render method :
Works like a charm, don't wait for React - they have their own policy and list of future pending , maybe will never be implemented :) |
@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 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. |
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 :/ |
@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. |
@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. |
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). |
@sophiebits Ah sorry, I shouldn't get ahead of myself! I'd love to figure out some resolution here. |
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. |
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} />;
}; |
For 2 Days research, I got a solution for this headache problem. Here is my solution |
Here is a POC, but unfortunately if this hook is called multiple times in the same component, it gives different ids. let id = 0;
const useMountId = () => {
const ref = useRef<string>();
if (!ref.current) {
ref.current = (++id).toString(10);
}
return ref.current;
} |
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?The text was updated successfully, but these errors were encountered: