Skip to content

Conversation

@jdeichert
Copy link
Contributor

@jdeichert jdeichert commented Jan 21, 2026

Motivations

This PR introduces an extra safety check for the Autocomplete v2 value prop.

There's a case in consumer code where they supplied an empty label (equivalent to this value={{ label: "" }}) instead of supplying undefined when nothing is selected. This empty label can lead to an infinite loop due to value being non-null, useAutocomplete was treating that as hasSelection=true which then led to an effect running unnecessarily, to clear the value with onChange(undefined). The consumer code then did the exact same thing in response to that onChange call, supplying an empty label, thus causing the loop.

This is now fixed by treating an empty/nullish value.label as an invalid value, making it match the unselected case just like if value was undefined. This prevents consumer code from accidentally triggering this code path leading to an infinite loop.

Ideally consumers don't supply invalid labels. However, it's possibly due to the fact that ReactHookForm does not allow passing undefined to the controller, so consumers are working around that in their own ways. Regardless, adding this precaution is a pretty cheap and effective way to avoid potential infinite loops that end up coming back to us for investigation.

Changes

Fixed

  • Prevent infinite loops when receiving an invalid value.label

Testing

  1. Pull this branch locally
  2. Open up the story: docs/components/Autocomplete/WebV2.stories.tsx
  3. Update TemplateFlat to the code example below
  4. View this story locally
    • Observe there's no infinite loop
  5. Repeat with this test story against master and you'll see an infinite loop
const TemplateFlat: ComponentStory<typeof Autocomplete> = () => {
  const [{ value }, setFormValue] = useState<{ value: string }>({ value: "" });
  const [inputValue, setInputValue] = useState("");

  console.log(`🔥 rerender`);

  return (
    <Content>
      <Heading level={4}>Flat, default layout</Heading>
      <Autocomplete
        version={2}
        placeholder="Search for a service"
        value={{ label: value, value }}
        onChange={v => {
          if (v) {
            setFormValue({ value: v.label });
          } else {
            setFormValue({ value: "" });
          }
        }}
        inputValue={inputValue}
        onInputChange={setInputValue}
        menu={[{ type: "options", options: flatOptions }]}
      />
    </Content>
  );
};

Changes can be tested via Pre-release


In Atlantis we use Github's built in pull request reviews.

When a single value is supplied, we should do an extra check to ensure the label is not empty. This reduces the likelihood of infinite loops when consumers supply incorrect values to Autocomplete v2.

An example of an invalid label is an empty string:

```tsx
value={{label: '' }}
```

This case above is a realistic case where the consumer did not have a selection, and instead should have supplied `undefined` in this case.

Checking for a valid label is just an extra precaution we're putting in place to prevent bad loops caused by our useAutocomplete internal hook logic.

describe("invalid value handling", () => {
describe("when inputValue is empty and value has empty label", () => {
it("does not call onChange", async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was added before the fix, and was failing as expected. Now passing 👍

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jan 21, 2026

Deploying atlantis with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8e8da81
Status: ✅  Deploy successful!
Preview URL: https://738c45df.atlantis.pages.dev
Branch Preview URL: https://job-148590-prevent-potential.atlantis.pages.dev

View logs

@jdeichert jdeichert marked this pull request as ready for review January 21, 2026 22:13
@jdeichert jdeichert requested a review from a team as a code owner January 21, 2026 22:13
@@ -1,3 +1,5 @@
/* eslint-disable max-statements */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea why I didn't do this to begin with haha

padding my number of lines changed I suppose

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure we could disable globally for all test files. That's for another day..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW the LLM went along and followed the same pattern lol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants