Skip to content

Commit ce094cd

Browse files
Fix EmojiPicker.Search external controlled values (#10)
* Reproduce issue in test * Fix controlled changes * Add console warning when mixing controlled and uncontrolled states * Update CHANGELOG.md
1 parent bca597a commit ce094cd

File tree

3 files changed

+62
-9
lines changed

3 files changed

+62
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## [Unreleased]
22

3+
- Fix `EmojiPicker.Search` controlled value not updating search results when updated externally. (e.g. other input, manually, etc)
4+
35
## [0.1.0] - 2025-03-18
46

57
- Initial release.

src/components/__tests__/emoji-picker.test.browser.tsx

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,31 @@ describe("EmojiPicker.Search", () => {
449449
});
450450

451451
it("should support a controlled search value", async () => {
452+
function Page() {
453+
const [search, setSearch] = useState("");
454+
455+
return (
456+
<DefaultPage
457+
searchOnChange={(event) => setSearch(event.target.value)}
458+
searchValue={search}
459+
/>
460+
);
461+
}
462+
463+
page.render(<Page />);
464+
465+
await expect.element(page.getByTestId("search")).toHaveValue("");
466+
467+
await page.getByTestId("search").fill("cat");
468+
await expect.element(page.getByTestId("search")).toHaveValue("cat");
469+
await expect.element(page.getByText("🐈")).toBeInTheDocument();
470+
471+
await page.getByTestId("search").fill("123456789");
472+
await expect.element(page.getByTestId("search")).toHaveValue("123456789");
473+
await expect.element(page.getByTestId("empty")).toBeInTheDocument();
474+
});
475+
476+
it("should support an external controlled search value", async () => {
452477
function Page() {
453478
const [search, setSearch] = useState("");
454479

@@ -457,13 +482,11 @@ describe("EmojiPicker.Search", () => {
457482
searchOnChange={(event) => setSearch(event.target.value)}
458483
searchValue={search}
459484
>
460-
<button
461-
data-testid="update-search"
462-
onClick={() => setSearch("hello")}
463-
type="button"
464-
>
465-
Update search
466-
</button>
485+
<input
486+
data-testid="controlled-search"
487+
onChange={(event) => setSearch(event.target.value)}
488+
type="text"
489+
/>
467490
</DefaultPage>
468491
);
469492
}
@@ -472,9 +495,13 @@ describe("EmojiPicker.Search", () => {
472495

473496
await expect.element(page.getByTestId("search")).toHaveValue("");
474497

475-
await page.getByTestId("update-search").click();
498+
await page.getByTestId("controlled-search").fill("cat");
499+
await expect.element(page.getByTestId("search")).toHaveValue("cat");
500+
await expect.element(page.getByText("🐈")).toBeInTheDocument();
476501

477-
await expect.element(page.getByTestId("search")).toHaveValue("hello");
502+
await page.getByTestId("controlled-search").fill("123456789");
503+
await expect.element(page.getByTestId("search")).toHaveValue("123456789");
504+
await expect.element(page.getByTestId("empty")).toBeInTheDocument();
478505
});
479506
});
480507

src/components/emoji-picker.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,23 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, EmojiPickerSearchProps>(
509509
store.set({ searchRef: ref });
510510
}
511511
}, []);
512+
const isControlled = typeof value === "string";
513+
const wasControlled = useRef(isControlled);
514+
515+
useEffect(() => {
516+
if (
517+
process.env.NODE_ENV !== "production" &&
518+
wasControlled.current !== isControlled
519+
) {
520+
console.warn(
521+
`EmojiPicker.Search is changing from ${
522+
wasControlled ? "controlled" : "uncontrolled"
523+
} to ${isControlled ? "controlled" : "uncontrolled"}.`,
524+
);
525+
}
526+
527+
wasControlled.current = isControlled;
528+
}, [isControlled]);
512529

513530
// Initialize search with a controlled or uncontrolled value
514531
useLayoutEffect(() => {
@@ -522,6 +539,13 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, EmojiPickerSearchProps>(
522539
});
523540
}, []);
524541

542+
// Handle controlled value changes
543+
useLayoutEffect(() => {
544+
if (typeof value === "string") {
545+
store.get().onSearchChange(value);
546+
}
547+
}, [value]);
548+
525549
const handleChange = useCallback(
526550
(event: ReactChangeEvent<HTMLInputElement>) => {
527551
onChange?.(event);

0 commit comments

Comments
 (0)