Intent to Ship: useId #111
Replies: 7 comments 25 replies
-
So facebook/react#20127 is not a bug then? There's also two use cases from the RFC that weren't adressed yet:
I was under the impression that these would be adressed (#9 (reply in thread)) but this intent to ship makes it sound like this won't happen anymore? |
Beta Was this translation helpful? Give feedback.
-
This is awesome! We have a solution to this in React Aria, but the implementation is quite complex. Excited to use this instead once we can drop older React versions. 🥳 I'm curious how it's implemented in React. How are the ids generated on the server so they match on the client? Is it a counter that resets at every React root? How do you handle async/suspense boundaries? We had to introduce an extra context provider to wrap these to ensure the ids are generated consistently no matter the loading order, but I guess React could do that in the Suspense component? Would be curious to know more about how it works! (I guess I could also go read the code haha... 😉) |
Beta Was this translation helpful? Give feedback.
-
This implementation might rely on a hardcoded list of ID attribute names so list your favorite ID related attributes (aria or not) here! |
Beta Was this translation helpful? Give feedback.
-
From Twitter: https://twitter.com/sebmarkbage/status/1451358419546431489 If we used the tree index approach, we'd need to involve every node (Fiber) in the ID generation. Suspense boundaries only is not enough. That's a long path. However, since we can assume that the number of children at each level is fixed (hydration should match exactly). We can allocate only as many bits for each level as there are children. E.g. you can think of it as an S-expression tree. That's how many bits we need. That might not actually lead to too long ID names. So we could try that approach too with same API. |
Beta Was this translation helpful? Give feedback.
This comment has been hidden.
This comment has been hidden.
-
Somewhat related to the comment above: #111 (comment) In Emotion we are, at the moment, conditionally wrapping (on the server) a top-level Emotion-aware component with a provider: This allowed us to provide a "zero-config" solution so far because this means that a separate cache gets created, automatically, per each server-side render (per request). Given the concerns about the React tree having to be identical on the client and on the server, for |
Beta Was this translation helpful? Give feedback.
-
As mentioned in the post, using counter to generate |
Beta Was this translation helpful? Give feedback.
-
This feature is now available in the latest alphas
useId
is an API for generating unique IDs on both the client and server, while avoiding hydration mismatches. We haven't documented it yet, but the API is probably what you expect:This solves an issue that already exists in React 17 and below, but it's even more important in React 18 because of how our streaming server renderer delivers HTML out-of-order. Solutions that may have worked previously, like using a counter to generate IDs, don't work in 18.0 because you can't rely on a consistent sequence.
@lunaruan implemented this API as
useOpaqueIdentifier
last year (facebook/react#17322), but because of some known bugs and limitations, we had intended to delay its release until after 18.0.We've now fixed all the known bugs, and have a plan to address some of the limitations, so we do intend to ship it in React 18.
useOpaqueIdentifier -> useId
In the original proposal,
useOpaqueIdentifier
returns an opaque value. On the server, it's a string. But on the client, it's a special object that warns if you attempt to access thetoString
-ed value in userspace (as opposed to passing it directly to a DOM attribute). The reason for this is because React needs to be able to detect when an id is referenced inside a tree that wasn't server-rendered, so that it can resolve server-client mismatches. In the current implementation, we do this with a brand check:obj.$$typeof === REACT_OPAQUE_ID_TYPE
. It also relies on the object'stoString
method to detect when the id is read on the client outside of a server-rendered tree (taking advantage of the fact that we don't modify attributes during hydration).However, we received feedback that this restriction was too limiting. Some of the drawbacks include:
useOpaqueIdentifier
hook instance per distinct form field. Example: Introducing an RFC for isomorphic IDs reactjs/rfcs#32 (comment)aria-labelledby
accept multiple ids via a space-separated list. If you attempt to do this today, React will warn when you concatenate the ids.To address both of these issues, we're changing the type to be a regular, non-opaque string.
React still needs to be able to detect these special ids when you pass them to an attribute. Instead of a brand check, when applying the attribute, React will check if the string includes a special sequence of characters (i.e. with[edit: Will likely go with a different implementation without this restriction.]str.indexOf(...) !== -1
). The rest of the algorithm is unchanged. This new substring mechanism means you can add extra stuff to the beginning or end of the string, as long as the original string is still intact.Now instead of generating a separate hook N times for N different ids, you can create a single base id for the whole form, then derive further ids from that one by appending a suffix:
In this example, because the React-generated id is globally unique, and the suffixes are locally unique, the derived ids are globally unique, too.
Beta Was this translation helpful? Give feedback.
All reactions