Before diving into the useId
API
let us get familiar with
the terms which we would be using in this blog.
Client side rendering:
In client-side rendering, the app is rendered by the client. When a browser receives a request for a page, it sends HTML, CSS and, JS code to be run in the browser. The script is loaded and the app becomes interactive. This is a way by which most of the apps are rendered.
Image is taken from a talk by Shaundai in React Conf 2021.
Server side rendering(SSR):
When the browser receives a request for a page on the server, the data is fetched for the entire application and all the react components are rendered to HTML. HTML is sent to the browser and users can see the content on the browser instead of a blank screen, thereby improving the user experience. This type of rendering is useful for heavily driven content apps.
Image is taken from a talk by Shaundai in React Conf 2021.
Refer the React Glossary to understand SSR more thoroughly.
Hydration
Hydration is a process of rendering our components and attaching event handlers. It is like watering the ‘dry’ HTML with the ‘water’ of interactivity and event handlers.
Only after the hydration phase users can interact with the application. More details about hydration can be read on the glossary.
Now, take a cup of coffee or tea and let us learn about the useId
and its journey!
Unique Id
To support accessibility, aria
, and a11y
APIs are widely used in the browser.
These APIs are heavily based on IDs to link components together.
aria-checked
, aria-label
, aria-labelledby
, and aria-describedby
etc,
all need IDs.
Most of the time, we generate IDs wherever needed using Math.random()
or any other external libraries.
However, server-side rendering makes it complicated due to Id mismatch between client and server.
Different approaches were followed to fix this issue like making ids as required props on components, this._rootNodeID, simple counter , etc. But none of them were able to address all the problems faced in server side rendering -
-
If we render on server-side and then hydrate, this could cause a hydration ID mismatch.
-
If we render on server-side on one part of the page and render on client side on another part of the page, both the IDs could be different, which are supposed to be the same.
-
If we conditionally render something with an ID, this might also cause a mismatch of IDs.
To fix these issues, Luna Ruan created a new hook useOpaqueIdentifier.
useOpaqueIdentifier API hook
useOpaqueIdentifier
API generates a unique ID based on whether the hook was called on the server or client.
If the hook is called during hydration, it generates an opaque object that will rerender the hook so that the IDs match.
Let’s checkout the example from RFC for isomorphic IDs.
As useOpaqueIdentifier
is a hook, we have to follow the hook rules. So we generate the Ids at the top.
Concerns in using useOpaqueIdentifier
-
To obey the hooks rules, we generate all the dynamic Ids before rendering the components. This is not a very good experience.
-
In some of the APIs like
aria-labelledby
, we need to concatenate the Ids. This is not supported in this API.
More work was done to fix these issues,
and thereby useOpaqueIdentifier
was renamed to useId
.
useId API
useId
API generates stable ids during server rendering and hydration to avoid mismatches.
Outside of server-rendered content, it falls back to a global counter.
From the above example, we see that:
-
We can create globally unique dynamic ids.
-
Instead of generating separate hook N times for N different ids, we can create a single base id for the whole form, then derive further ids from that one by appending a suffix.
The dynamic Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. More details of Id generation algorithm can be found in this PR.
A brief explanation of how the Id generation is implemented can be checked out in this discussion.