Skip to content

Commit 234d849

Browse files
committed
debounce fix
1 parent f4dcd97 commit 234d849

File tree

4 files changed

+89
-56
lines changed

4 files changed

+89
-56
lines changed

components/app-registrations/CredentialStatusGrid.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ import {
1515
} from "@fluentui/react-components";
1616

1717
import { useIsAuthenticated } from "@azure/msal-react";
18-
import { useState, useMemo } from "react";
18+
import { useState, useMemo, useRef } from "react";
1919
import useSWR from "swr";
2020

2121
import { Application, generateApplications } from "./CredentialStatusGrid.data-model";
2222
import { credentialStatusColumns } from "./CredentialStatusGrid.columns";
2323
import { fetcher, ODataResponse } from "@/lib/utils/msGraphFetcher";
2424
import { graphConfig } from "@/lib/msalConfig";
2525
import { SkeletonGrid } from "../SkeletonGrid";
26-
import { useDebouncedValue } from "@/lib/utils/common";
26+
import useDebounce from "@/lib/utils/common";
2727

2828
function useAuthenticatedSWR<T>(url: string, isAuthenticated: boolean) {
2929
return useSWR<ODataResponse<T>>(isAuthenticated ? url : null, fetcher);
@@ -97,10 +97,17 @@ function useFilteredCredentials(
9797

9898
export default function CredentialStatusGrid() {
9999
const isAuthenticated = useIsAuthenticated();
100+
100101
const [searchTerm, setSearchTerm] = useState("");
101-
const debouncedSearchTerm = useDebouncedValue(searchTerm, 300);
102-
const onSearchChange = (e: React.ChangeEvent<HTMLInputElement>) =>
103-
setSearchTerm(e.target.value);
102+
const inputRef = useRef<HTMLInputElement>(null);
103+
const debouncedSetSearchTerm = useDebounce((value: string) => {
104+
setSearchTerm(value);
105+
}, 300);
106+
const handleChange = () => {
107+
if (inputRef.current) {
108+
debouncedSetSearchTerm(inputRef.current.value);
109+
}
110+
};
104111

105112
const {
106113
data: appCredentialData,
@@ -119,7 +126,7 @@ export default function CredentialStatusGrid() {
119126
const filteredItems = useFilteredCredentials(
120127
isAuthenticated,
121128
appCredentialData?.value,
122-
debouncedSearchTerm
129+
searchTerm
123130
);
124131

125132
if (appCredentialIsLoading) {
@@ -177,9 +184,9 @@ export default function CredentialStatusGrid() {
177184
type="text"
178185
size="small"
179186
contentBefore={<SearchRegular />}
180-
onChange={onSearchChange}
187+
ref={inputRef}
188+
onChange={handleChange}
181189
placeholder="Filter by display name"
182-
value={searchTerm}
183190
/>
184191
</div>
185192

components/enterprise-applications/SamlStatusGrid.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ import {
1515
} from "@fluentui/react-components";
1616

1717
import { useIsAuthenticated } from "@azure/msal-react";
18-
import { useState, useMemo } from "react";
18+
import { useState, useMemo, useRef } from "react";
1919
import useSWR from "swr";
2020

2121
import { SamlStatus, generateSamlStatus } from "./SamlStatusGrid.data-model";
2222
import { samlStatusColumns } from "./SamlStatusGrid.columns";
2323
import { fetcher, ODataResponse } from "@/lib/utils/msGraphFetcher";
2424
import { graphConfig } from "@/lib/msalConfig";
2525
import { SkeletonGrid } from "../SkeletonGrid";
26-
import { useDebouncedValue } from "@/lib/utils/common";
26+
import useDebounce from "@/lib/utils/common";
2727

2828
function useAuthenticatedSWR<T>(url: string, isAuthenticated: boolean) {
2929
return useSWR<ODataResponse<T>>(isAuthenticated ? url : null, fetcher);
@@ -54,10 +54,17 @@ function useFilteredSamlStatus(
5454

5555
export default function SamlStatusGrid() {
5656
const isAuthenticated = useIsAuthenticated();
57+
5758
const [searchTerm, setSearchTerm] = useState("");
58-
const debouncedSearchTerm = useDebouncedValue(searchTerm, 300);
59-
const onSearchChange = (e: React.ChangeEvent<HTMLInputElement>) =>
60-
setSearchTerm(e.target.value);
59+
const inputRef = useRef<HTMLInputElement>(null);
60+
const debouncedSetSearchTerm = useDebounce((value: string) => {
61+
setSearchTerm(value);
62+
}, 300);
63+
const handleChange = () => {
64+
if (inputRef.current) {
65+
debouncedSetSearchTerm(inputRef.current.value);
66+
}
67+
};
6168

6269
const {
6370
data: samlStatusData,
@@ -76,7 +83,7 @@ export default function SamlStatusGrid() {
7683
const filteredItems = useFilteredSamlStatus(
7784
isAuthenticated,
7885
samlStatusData?.value,
79-
debouncedSearchTerm
86+
searchTerm
8087
);
8188

8289
if (samlStatusIsLoading) {
@@ -134,9 +141,9 @@ export default function SamlStatusGrid() {
134141
type="text"
135142
size="small"
136143
contentBefore={<SearchRegular />}
137-
onChange={onSearchChange}
144+
ref={inputRef}
145+
onChange={handleChange}
138146
placeholder="Filter by display name"
139-
value={searchTerm}
140147
/>
141148
</div>
142149

components/enterprise-applications/app-role-permissions-grid/AppRolePermissionsGrid.tsx

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from "@fluentui/react-components";
1919

2020
import { useIsAuthenticated } from "@azure/msal-react";
21-
import { useState, useMemo } from "react";
21+
import { useState, useMemo, useRef } from "react";
2222
import useSWR from "swr";
2323

2424
import {
@@ -33,7 +33,7 @@ import { graphConfig } from "@/lib/msalConfig";
3333
import { appRoleColumns } from "./AppRolePermissionsGrid.columns";
3434
import { findEntraOpsClassificationByPermission } from "@/lib/utils/entraOpsHelper";
3535
import { SkeletonGrid } from "@/components/SkeletonGrid";
36-
import { useDebouncedValue } from "@/lib/utils/common";
36+
import useDebounce from "@/lib/utils/common";
3737

3838
function useAuthenticatedSWR<T>(url: string, isAuthenticated: boolean) {
3939
return useSWR<ODataResponse<T>>(isAuthenticated ? url : null, fetcher);
@@ -108,10 +108,17 @@ function useFilter(
108108

109109
export default function AppRolePermissionsGrid() {
110110
const isAuthenticated = useIsAuthenticated();
111+
111112
const [searchTerm, setSearchTerm] = useState("");
112-
const debouncedSearchTerm = useDebouncedValue(searchTerm, 300);
113-
const onSearchChange = (e: React.ChangeEvent<HTMLInputElement>) =>
114-
setSearchTerm(e.target.value);
113+
const inputRef = useRef<HTMLInputElement>(null);
114+
const debouncedSetSearchTerm = useDebounce((value: string) => {
115+
setSearchTerm(value);
116+
}, 300);
117+
const handleChange = () => {
118+
if (inputRef.current) {
119+
debouncedSetSearchTerm(inputRef.current.value);
120+
}
121+
};
115122

116123
const {
117124
data: selectedServicePrincipalData,
@@ -144,18 +151,18 @@ export default function AppRolePermissionsGrid() {
144151
isAuthenticated,
145152
appRolesAssignedToData?.value,
146153
selectedServicePrincipalData?.value,
147-
debouncedSearchTerm
154+
searchTerm
148155
);
149156

150-
if (appRolesAssignedToIsLoading) {
151-
// Optionally render a skeleton or partial grid
152-
return (
153-
<div style={{ margin: "6px" }}>
154-
<Body1>Loading...</Body1>
155-
<SkeletonGrid columns={5} />
156-
</div>
157-
);
158-
}
157+
if (appRolesAssignedToIsLoading) {
158+
// Optionally render a skeleton or partial grid
159+
return (
160+
<div style={{ margin: "6px" }}>
161+
<Body1>Loading...</Body1>
162+
<SkeletonGrid columns={5} />
163+
</div>
164+
);
165+
}
159166

160167
return (
161168
<div>
@@ -180,9 +187,9 @@ export default function AppRolePermissionsGrid() {
180187
type="text"
181188
size="small"
182189
contentBefore={<SearchRegular />}
183-
onChange={onSearchChange}
190+
ref={inputRef}
191+
onChange={handleChange}
184192
placeholder="Filter by permission"
185-
value={searchTerm}
186193
/>
187194
</div>
188195

lib/utils/common.ts

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
1-
import { useEffect, useState } from "react";
1+
// useDebounce.ts
2+
import { useRef, useEffect, useCallback } from "react";
23

3-
export function useDebouncedValue(
4-
value: string,
5-
delay: number,
6-
minLength: number = 3
7-
): string {
8-
const [debouncedValue, setDebouncedValue] = useState<string>("");
9-
10-
useEffect(() => {
11-
if (value.length >= minLength) {
12-
const handler = setTimeout(() => {
13-
setDebouncedValue(value);
14-
}, delay);
15-
16-
return () => {
17-
clearTimeout(handler);
18-
};
19-
} else {
20-
setDebouncedValue(""); // To reset when under minLength
4+
/**
5+
* Custom hook to debounce a function.
6+
* @param callback The function to debounce.
7+
* @param delay The debounce delay in milliseconds.
8+
* @returns The debounced function.
9+
*/
10+
function useDebounce(callback: (...args: string[]) => void, delay: number) {
11+
const timeoutRef = useRef<number | undefined>();
12+
13+
const debouncedFunction = useCallback(
14+
(...args: string[]) => {
15+
if (timeoutRef.current) {
16+
clearTimeout(timeoutRef.current);
17+
}
18+
timeoutRef.current = window.setTimeout(() => {
19+
callback(...args);
20+
}, delay);
21+
},
22+
[callback, delay]
23+
);
24+
25+
useEffect(() => {
26+
// Cleanup on unmount
27+
return () => {
28+
if (timeoutRef.current) {
29+
clearTimeout(timeoutRef.current);
2130
}
22-
}, [value, delay, minLength]);
23-
24-
return debouncedValue;
25-
}
31+
};
32+
}, []);
33+
34+
return debouncedFunction;
35+
}
36+
37+
export default useDebounce;

0 commit comments

Comments
 (0)