-
Notifications
You must be signed in to change notification settings - Fork 46.8k
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
Generating unique ID's and SSR (for a11y and more) #5867
Comments
I currently solve this by generating a UID in a components constructor, which is then used as the basis for all my DOM IDs -- as seen here: https://github.com/titon/toolkit/blob/3.0/src/Component.js#L115 I then pass this UID along to all the children via contexts, which allows them to stay consistent: https://github.com/titon/toolkit/blob/3.0/src/components/accordion/Header.js#L62 I would then end up with IDs like IMO, this kind of functionality doesn't really need to be part of React. |
I think your solution still suffers from the exact problem I'm talking about here. Your code seems to depend on this |
Pulling from #4000, which is a bit long... I believe the major criteria were:
From #1137,
Seems it's gotten a bit beyond that now :) |
@jquense Then simply write a UID generator function that returns the same value on the server or the client.
|
@milesj thanks for the suggestions tho in this case that is only helpful if you have one instance of a component on the page. @rileyjshaw I think that sums up the issue very succinctly, thank you. The best solution is the root store that passes ID's via context, but that's a bit cumbersome for each library to invent and provide unless there was some consensus in the community. |
You're over-thinking this a bit. All the reasons you keep stating are easily fixable. Assuming that the server and client render everything in the same order (not sure why they wouldn't), just have a separate function module that generates and keeps track of things.
And in the component.
And if for some reason you want individually incrementing counters per component, then just use a |
@milesj I appreciate your suggestions and efforts at a solution but you may not be familiar enough with the problem. The simple incrementing counter doesn't work, if only because ppl don't usually render exactly the same thing on the client and server. I think you might want to read the linked issues, specifically #4000. I am well aware of the issues and the possible solutions, if there was an simple straightforward solution that worked I wouldn't be here. |
I think @jquense makes a valid point about us needing a good solution here - it is currently overly painful to coordinate psudorandom identifiers for component. The problem is exacerbated when different components choose different solutions for solving the coordination problem. We see the exact same problem when components use the clock (for instance, to print relative times). We also see similar use cases for things like sharing flux stores across component libraries. Anyway, I think it would be good to find a real/supported solution for this use case. |
What about |
Sorry if this comment is ignorant. I'm a bit of a noob, but if the key path is always unique and immutable, would we need a counter at all? So, in short: React.getUniqueId(this[, localId]) This would return a unique id based on the key path, and the optional second argument, if provided. The function would be pure, in that it would always return the same output given the same inputs, I.E. no counter. If more than one ID was needed within the same component, the optional second argument could be provided to differentiate between the two. Of course if key paths aren't immutible and unique, then this obviously won't work. |
@jimfb did you, or the rest of ya'll at FB have any thoughts about a potential API for this sort thing? I might be able to PR something but not quite sure what everyone thinks the reach should be. Coordinating identifiers is a different sort of thing that coordinating a some sort of server/client cache, which is what suggests itself to me thinking about the Date or flux store use-case. |
Here's what I did: let idCounter = (typeof window !== 'undefined' && window.__ID__) || 0;
function id() {
return ++idCounter;
}
export default id; const seed = id();
const rendered = renderToString(<DecoratedRouterContext {...renderProps}/>);
const markup = `
<div id="container">${rendered}</div>
<script type="text/javascript" charset="UTF-8">window.__ID__ = ${JSON.stringify(seed)};</script>
`; in my components I just import id from '../utils/id.js';
render() {
nameID = id();
return (
<div>
<label htmlFor={nameId}>Name</label>
<input id={nameId} />
</div>
);
} I agree that it would be nice if react provided a standard way of syncing values between client and server. That being said, I don't know what the API would look like and this was pretty easy to do. |
I hacked up something recently that seems to do the trick. I noticed that when rendered server-side, the DOM nodes will all have the let index = 0;
static uniqueIdForComponent(component)
{
let node = ReactDOM.findDOMNode(component);
if (node) {
if (node.hasAttribute("data-reactid")) {
return "data-reactid-" + node.getAttribute("data-reactid");
}
}
return `component-unique-id-${index++}`;
} There is undoubtedly a more optimized way to do this, but from recent observation, the methodology appears to be sound; at least for my usage. |
@n8mellis Unfortunately |
I don’t see what kind of IDs could be provided by React that can’t be determined by the user code.
That sounds like the crux of the problem? Rendering different things on the client and on the server is not supported, at least during the first client-side render. Nothing prevents you, however, from first rendering a subset of the app that is the same between client and server, and then do a From #4000:
The solution is to isolate the counter between SSR roots. For example by providing So I think we can close this. |
@gaearon I don't know that the issue is that it can't be solved outside of react, but the React implementing it can go a long way towards improving the ergonomics of implementing a11y in React apps. For instance if there was a react API, we could use it in react-bootstrap, and not have to require a user to add a seemingly-to-them unnecessary umpteenth Provider component to their root, to use RB's ui components. Plus if others are going to do it thats x more Admittedly, this (for me anyway) is a DX thing, but I think that is really important for a11y adjacent things. A11y on the web is already an uphill battle that most just (sadly) give up on, or indefinitely "defer" until later. Having a unified way to identify components would go a long way in react-dom to reducing one of big hurdles folks have implementing even basic a11y support, needing to invent consistent, globally unique, but semantically meaningless identifiers. It's not clear to me at this point that React is necessarily better positioned to provide said identifiers (I think in the past it was a bit more), but it certainly can help reduce friction in a way userland solutions sort of can't. |
I think to progress further this would need to be an RFC. https://github.com/reactjs/rfcs If someone who's directly motivated to fix it (e.g. a RB maintainer :-) could think through the API, we could discuss it and maybe even find other use cases as well. (I agree it's annoying to have many providers, @acdlite also mentioned this in other context a few weeks ago.) |
This never really worker for SSR applications because different ID is generated on server side than on client side. There are some ways to make it work properly but it looks too complicated. Let the user pass it down. See more: facebook/react#5867
This never really worker for SSR applications because different ID is generated on server side than on client side. There are some ways to make it work properly but it looks too complicated. Let the user pass it down. See more: facebook/react#5867
I've tried to store id counter in the local scope of the component but it seems won't work too:
|
Just to follow up on this, we are testing a potential solution to this internally but not ready to draft an RFC yet. |
Im wrote hook for generate ID with SSR supports. Gist with example — https://gist.github.com/yarastqt/a35261d77d723d14f6d1945dd8130b94 |
@Merri it's not true, i increase counter only on client side |
Tangential to this thread, if anyone is interested, I wrote a lil' context/hook that returns an RNG seeded by the value you pass into its provider, and it's served me well in keeping random values between server and client: import React, { useContext } from "react";
import seedrandom from "seedrandom";
const Random = React.createContext("seed");
const rngCache: {
[key: string]: ReturnType<seedrandom>;
} = {};
export const useRandom = (): [ReturnType<seedrandom>, string] => {
const seed = useContext(Random);
let rng = rngCache[seed];
if (!rng) {
rng = rngCache[seed] = seedrandom(seed);
}
return [rng, seed];
};
export default Random; In your parent component: <Random.Provider value="your seed">
{/* ... */}
</Random.Provider> Then, in your nested components: const [rng] = useRandom();
// `rng.quick()`
// etc... Hope it's useful to someone! 😄 |
I am using |
There are plans currently to ship |
|
Howdy ya'll,
tl dr: please provide a way to coordinate pseudo-random identifiers across the client and server
This issue has been discussed a bit before (#1137, #4000) but I continually run into this issue, trying to build libraries that provide accessible components by default. The react component model generally speaking offers a big opportunity to raise the often low bar for accessibility in the library and widget world, see experiments like @ryanflorence's react-a11y.
For better or for worse the aria, and a11y API's in the browser are heavily based on using ID's to link components together.
aria-labelledby
,aria-describedby
,aria-owns
,aria-activedescendent
, and so on all need's ID's. In a different world we would just generate ids where needed and move on, however server-side rendering makes that complicated, because any generated ID is going to cause a mismatch between client/server.We've tried a few different approaches to address some of this, one is making id's required props on components that need them. That gets kinda ugly in components that need a few id's but moreso it annoys users. Its unfortunate because if we could generate deterministic id's we could just provide more accessible components by default.
The frustrating part is that the component generally has all the information it needs to just set the various aria info necessary to make the component usable with a screen reader, but are stymied by not having the user provide a bunch of globally unique ids'
So far only really reasonable approaches I've seen are @syranide's solution of a root ID store, and using
_rootID
. The latter obviously has problems. The former doesn't scale well for library authors. Everyones' root App component is already wrapped in a Router, Provider, etc, having every library use their own root level ID provider is probably not super feasible and annoying to users.It seems like the best way to do this would be if React (or a React addon) could just provide a consistent first class way to get a unique identifier for a component, even if it is just a base64 of the node's _rootID.
thanks for all the hard work everyone!
The text was updated successfully, but these errors were encountered: