primitives icon indicating copy to clipboard operation
primitives copied to clipboard

Select component crashes when translated on Chrome

Open joshsny opened this issue 1 year ago • 7 comments
trafficstars

Bug report

Current Behavior

With a translation extension enabled, or any extension that modifies text on the page, changing the value of a Select component throws the following exception:

DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

This means the user is unable to change the value.

Expected behaviour

The user can change the value of the Select component.

Reproducible example

  1. Set your active language to be something other than English in Chrome
  2. Visit Select component in docs: https://www.radix-ui.com/themes/docs/components/select
  3. Try to change the value of the Select

Suggested solution

Select functionality should depend on the value only and be independent of the label

joshsny avatar Dec 08 '23 20:12 joshsny

Sorry to tag a few people, just want to bring this to your attention in case it got missed as it seems quite important. @benoitgrelard @jjenzz @andy-hook @peduarte @chancethedev @cappuc @Nazeh

This is currently affecting a lot of users of our site (~1% of users seem to use a translation extension on our site) but I imagine is an issue for almost any site using Radix Select (including the docs).

If anyone has ideas for a fix for this I am happy to contribute a PR.

joshsny avatar Dec 18 '23 15:12 joshsny

This is something we're hitting on our site too. As a temporary workaround we've moved over to HeadlessUI's combobox for this one use-case as it doesn't have the same problem.

We've encountered very similar issues in the past with our own components. When it's happened it's because we've had a an element with text and some other component inside it, which we're conditionally rendering. For instance a loading spinner on a button

<button>
  {children}
  {isLoading && <Spinner/>}
</button>

The extension has modified the text node (children) and when React tries to remove or add the other element (the spinner) its reconciliation breaks because the node has changed.

We worked around this by wrapping the text-containing part in a span, so it's separate from the conditionally rendered element. This doesn't seem possible to do as a consumer of <Select> (I tried). But may be is something that can be changed internally in the rendered markup.

AlexKMarshall avatar Dec 19 '23 09:12 AlexKMarshall

We've managed to work around this on our site by wrapping the label in a span:

<Item>
  <ItemText>
    <span>{label}</span>
  </ItemText>
</Item>

But this might not work for all use cases

benj-dobs avatar Jan 03 '24 13:01 benj-dobs

Same what I did just covered my SelectItem with spans

❌ BEFORE:

  <SelectItem value="main">Main Pool</SelectItem>

✅ AFTER:

  <SelectItem value="main">
    <span>Main Pool</span>
  </SelectItem>

I think it's neccessary to add for all items extra span for text nodes during Google Translate bug.

rashdeva avatar Jan 14 '24 00:01 rashdeva

It doesn't look like something we could fix, or has anybody has a more canonical example of the issue here without an extention, ie. simulating what the extension is doing which would let us investigate?

If not, I'm inclined to close the issue.

Let me know!

benoitgrelard avatar Mar 08 '24 13:03 benoitgrelard

@benoitgrelard An extension is actually not required to repro this issue, just using the native translations built into Chrome and following the reproduction steps will trigger the issue.

This means that any users who use a website not in their default locale will not experience a functioning select component.

Here is a video reproducing the error: Screen Cast 2024-05-21 at 1 07 59 PM

joshsny avatar May 21 '24 09:05 joshsny

This error also occurs in Safari, using the browser's built-in translation feature.

Firefox's translation feature works differently and does not cause these errors.

(In the end our "solution" was to add translate="no" to the <html> element to prevent Safari and Chrome from translating the page at all. We were encountering these issues too frequently, not only with Radix UI but throughout our React app.)

benj-dobs avatar May 27 '24 09:05 benj-dobs

We also just hit this lately. I created this sandbox @benoitgrelard hoping it helps with investigating and fixing.

On our end, we realised thanks to comments here that the issue comes from the placeholder not being wrapped in a span. We re-implemented the Select.Value to wrap the placholder in a span when it is only a string. We might have to do the same for children, following https://github.com/radix-ui/primitives/issues/2578#issuecomment-1890801041

bobinette avatar Jul 12 '24 08:07 bobinette

This pitfall should definitely be commented in the docs. It's very hard to reproduce this issue if a user reports this error and you have no idea if they were using the translator or not

CodingDive avatar Jan 04 '25 11:01 CodingDive

Agreeing with @CodingDive, this is quite hard to debug and had no clue why this happend.

Ruitjes avatar Jan 15 '25 21:01 Ruitjes

This isn't anything to do with Radix, rather a larger compatibility issue between React (and many other libraries that behave similarly) and how browsers perform translations.

React renders HTML and makes updates to the resulting DOM. If that DOM changes outside of React—which occurs when you use the browser's translate feature—and React subsequently tries to make an update, it's possible that the node it's looking for no longer exists or may even be the wrong node.

There's not really much we can do here, and it's been a long-standing issue . My understanding is that improvements have been made in React 19 to reduce potential issues, but so long as the browser manipulates the DOM in ways that React cannot inspect or control, I suspect this will remain unresolved. See https://issues.chromium.org/issues/41407169 for more information.

chaance avatar Jan 16 '25 18:01 chaance

I understand the underlying issue is complicated and requires compatibility in React and how Google Translate works.

But there are some things which can be improved in Radix UI to be more resilient to DOM changes

This issue mainly happens in Select component due to conditional rendering, for eg,

In SelectValue component due to how placeholder is rendered https://github.com/radix-ui/primitives/blob/7e2adc6dee70d175191ace6ce60e9aa86e46f2f2/packages/react/select/src/select.tsx#L387

Modifying the impl. according to below comment should make the component more resilient - https://github.com/facebook/react/issues/11538#issuecomment-390386520

Updated code would look something like -

{shouldShowPlaceholder(context.value) ? <span>{placeholder}</span> : children} 

manish-baghel avatar May 10 '25 03:05 manish-baghel