diff --git a/src/components/mui/__tests__/search-input.test.js b/src/components/mui/__tests__/search-input.test.js
index ad8f853..109a821 100644
--- a/src/components/mui/__tests__/search-input.test.js
+++ b/src/components/mui/__tests__/search-input.test.js
@@ -12,10 +12,11 @@
* */
import React from "react";
-import { render, screen } from "@testing-library/react";
+import { render, screen, act } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import SearchInput from "../search-input";
+import { DEBOUNCE_WAIT } from "../../../utils/constants";
describe("SearchInput", () => {
test("renders with custom placeholder", () => {
@@ -58,4 +59,39 @@ describe("SearchInput", () => {
rerender();
expect(screen.getByDisplayValue("new")).toBeInTheDocument();
});
+
+ describe("debounced prop", () => {
+ beforeEach(() => jest.useFakeTimers());
+ afterEach(() => jest.useRealTimers());
+
+ test("calls onSearch after debounce delay when debounced is true", async () => {
+ const onSearch = jest.fn();
+ const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
+ render();
+ const input = screen.getByPlaceholderText("Search...");
+ await user.type(input, "something");
+ expect(onSearch).not.toHaveBeenCalled();
+ act(() => jest.advanceTimersByTime(DEBOUNCE_WAIT));
+ expect(onSearch).toHaveBeenCalledWith("something");
+ });
+
+ test("does not call onSearch on Enter when debounced is true", async () => {
+ const onSearch = jest.fn();
+ const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
+ render();
+ const input = screen.getByPlaceholderText("Search...");
+ await user.type(input, "something{Enter}");
+ expect(onSearch).not.toHaveBeenCalled();
+ });
+
+ test("does not call onSearch on typing without Enter when not debounced", async () => {
+ const onSearch = jest.fn();
+ const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
+ render();
+ const input = screen.getByPlaceholderText("Search...");
+ await user.type(input, "something");
+ act(() => jest.runAllTimers());
+ expect(onSearch).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/src/components/mui/search-input.js b/src/components/mui/search-input.js
index e83e249..defb8bd 100644
--- a/src/components/mui/search-input.js
+++ b/src/components/mui/search-input.js
@@ -11,29 +11,44 @@
* limitations under the License.
* */
-import React, { useEffect, useState } from "react";
-import { TextField, IconButton } from "@mui/material";
+import React, { useEffect, useState, useMemo } from "react";
+import { TextField, IconButton, InputAdornment } from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import ClearIcon from "@mui/icons-material/Clear";
+import { debounce } from "lodash";
+import { DEBOUNCE_WAIT } from "../../utils/constants";
-const SearchInput = ({ term, onSearch, placeholder = "Search..." }) => {
+const SearchInput = ({ term, onSearch, placeholder = "Search...", debounced }) => {
const [searchTerm, setSearchTerm] = useState(term);
useEffect(() => {
setSearchTerm(term || "");
}, [term]);
- const handleSearch = (ev) => {
- if (ev.key === "Enter") {
- onSearch(searchTerm);
- }
- };
-
const handleClear = () => {
+ onSearchDebounced?.cancel();
setSearchTerm("");
onSearch("");
};
+ const onSearchDebounced = useMemo(
+ () => debounced ? debounce((value) => onSearch(value), DEBOUNCE_WAIT) : null,
+ [onSearch, debounced]
+ );
+
+ useEffect(() => () => onSearchDebounced?.cancel(), [onSearchDebounced]);
+
+ const handleChange = (value) => {
+ setSearchTerm(value);
+ if (debounced) onSearchDebounced(value);
+ };
+
+ const handleKeyDown = (ev) => {
+ if (!debounced && ev.key === "Enter") {
+ onSearch(searchTerm);
+ }
+ };
+
return (
{
placeholder={placeholder}
slotProps={{
input: {
- endAdornment: term ? (
-
-
-
- ) : (
-
+ startAdornment: (
+
+
+
+ ),
+ endAdornment: term && (
+
+
+
+
+
)
}
}}
- onChange={(event) => setSearchTerm(event.target.value)}
- onKeyDown={handleSearch}
+ onChange={(ev) => handleChange(ev.target.value)}
+ onKeyDown={handleKeyDown}
fullWidth
sx={{
"& .MuiOutlinedInput-root": {