react: Bug: useId IDs have special characters that conflict with certain DSLs (like CSS)
The ID scheme for the useId hook returns IDs that look like ":S1:", but : is not a valid character for CSS identifiers and must be escaped. On client-only components, you can wrap the ID in CSS.escape but the CSS global object doesn’t exist on the server so this doesn’t work for server-component renders.
React version: 18.2
Steps To Reproduce
- Try utilizing the id’s created by
useIdin CSS identifiers. - It doesn’t work.
Link to code example: https://stackblitz.com/edit/react-ts-zhtk6u?file=Example.tsx
The current behavior
The current implementation of useId creates IDs with special characters which can conflict with certain DSLs (in this example, CSS) that React has to commonly interact with.
The expected behavior
Don’t use special characters in IDs created by useId.
About this issue
- Original URL
- State: open
- Created a year ago
- Reactions: 5
- Comments: 16 (1 by maintainers)
@gaearon I figured the naming scheme was to avoid any potential conflict. My preference would be an ID generation model that uses a non-special character wrapping or just a GUID.
e.g.
REACTIDS1orf2ba2bd18ae847a0beb78f57f58d836dHowever, if the wrapping characters were changed from
:(colon) to either-(dash) or_(underscore) then the IDs at least wouldn’t conflict with CSS.e.g.
_S1_or-S1-I know React itself isn’t really supposed to be making changes specific to the web, but I do think underscores in particular are far less likely to conflict with other DSLs. I also feel like this is a good use-case for
useIdwhich is made more challenging because of this issue (this case is actually the first time I’ve neededuseId, personally).As an element id it works, as long as you are in JS. but not when you are trying to write a CSS selector. As
#:gen-id: {}is not a valid CSS sectorThat functionally works but… it’s not really a good idea. The problem here is that React owns the generation of the ID. If you put this function in your code and then React changes how their ID generation implementation works and then you update React, you could end up with overlapping IDs. Like say that these become two separate IDs that React may return:
as:df&as-df. Your function would convert them both toasdf, meaning that you’d have two copies of the same ID. If you useCSS.escapeas is used in my example, there’s no possible risk of that issue but of courseCSS.escapedoesn’t work on the server. You could reimplement that, but you really shouldn’t modify the ID.In general though, this is definitely something that React must own the fix of so that the rest of us don’t have to worry about it, or monkeypatch primitive hooks, or have to be concerned with how to handle the logic on the client & server separately for a DSL as commonly needed with React as CSS.
Please fix it
I would like to note if you’d like to select an element in the DOM where it has its ID generated by
useId, you can do something like the following:https://github.com/typehero/typehero/blob/main/apps/web/src/components/carousel.tsx
Where
buttonRightSelectoris the string ID generated. Just a suggestion if you get stuck on it not working as a valid selector.it’s unfortunate that
:was selected as the delimiter. stops us from targeting the element within a inline style blockresult:
This is what we’ve had to do and cost us a lot of wasted time updating all of our tests to dodge this strange decision of putting colons in id values
To be clear, the ids generated by react are valid, and do work correctly for accessibility purposes. In your tests, you can use
getElementByIdto find the element instead ofquerySelectorwith a manually prepended#. Alternatively if you need a more complex selector, the id can be escaped with the built inCSS.escapefunction: