diff --git a/common/changes/@itwin/imodel-browser-react/itwin-multiple-request_2024-12-05-17-57.json b/common/changes/@itwin/imodel-browser-react/itwin-multiple-request_2024-12-05-17-57.json
new file mode 100644
index 00000000..c3fdc96c
--- /dev/null
+++ b/common/changes/@itwin/imodel-browser-react/itwin-multiple-request_2024-12-05-17-57.json
@@ -0,0 +1,10 @@
+{
+ "changes": [
+ {
+ "packageName": "@itwin/imodel-browser-react",
+ "comment": "Avoid unnecessary refetchs to API",
+ "type": "patch"
+ }
+ ],
+ "packageName": "@itwin/imodel-browser-react"
+}
\ No newline at end of file
diff --git a/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.test.tsx b/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.test.tsx
index 494533b1..f6ae2a05 100644
--- a/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.test.tsx
+++ b/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.test.tsx
@@ -55,4 +55,43 @@ describe("ITwinGrid", () => {
expect(wrapper.getByRole("table")).toBeDefined();
expect(wrapper.getAllByRole("row").length).toEqual(3); // First row is header
});
+
+ it("should not refetch iTwins favorites when component rerenders", async () => {
+ // Arrange
+ const fetchMore = jest.fn();
+ jest.spyOn(useITwinData, "useITwinData").mockReturnValue({
+ iTwins: [],
+ status: DataStatus.Complete,
+ fetchMore,
+ });
+ // Act
+ const signal = new AbortController().signal;
+ const wrapper = render(
+
+ );
+ wrapper.rerender(
+
+ );
+
+ expect(fetch).toHaveBeenCalledTimes(1);
+ expect(fetch).toHaveBeenCalledWith(
+ "https://qa-api.bentley.com/itwins/favorites?subClass=Project",
+ {
+ headers: {
+ Accept: "application/vnd.bentley.itwin-platform.v1+json",
+ "Cache-Control": "",
+ authorization: "accessToken",
+ },
+ signal: signal,
+ }
+ );
+ });
});
diff --git a/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.tsx b/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.tsx
index 25109441..86ea62b5 100644
--- a/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.tsx
+++ b/packages/modules/imodel-browser/src/containers/ITwinGrid/ITwinGrid.tsx
@@ -122,7 +122,7 @@ export const ITwinGrid = ({
removeITwinFromFavorites,
shouldRefetchFavorites,
resetShouldRefetchFavorites,
- } = useITwinFavorites(accessToken, apiOverrides);
+ } = useITwinFavorites(accessToken, apiOverrides?.serverEnvironmentPrefix);
const strings = _mergeStrings(
{
diff --git a/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinData.ts b/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinData.ts
index f0f6f4fa..cac36ba4 100644
--- a/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinData.ts
+++ b/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinData.ts
@@ -36,6 +36,7 @@ export const useITwinData = ({
resetShouldRefetchFavorites,
}: ProjectDataHookOptions) => {
const data = apiOverrides?.data;
+ const serverEnvironmentPrefix = apiOverrides?.serverEnvironmentPrefix;
const [projects, setProjects] = React.useState([]);
const [status, setStatus] = React.useState();
const filteredProjects = useITwinFilter(projects, filterOptions);
@@ -69,7 +70,7 @@ export const useITwinData = ({
setProjects([]);
setPage(0);
setMorePages(true);
- }, [accessToken, requestType, iTwinSubClass, data, apiOverrides]);
+ }, [accessToken, requestType, iTwinSubClass, data, serverEnvironmentPrefix]);
React.useEffect(() => {
if (!morePages) {
@@ -100,7 +101,7 @@ export const useITwinData = ({
? ""
: `&$search=${filterOptions}`;
const url = `${_getAPIServer(
- apiOverrides
+ serverEnvironmentPrefix
)}/itwins/${endpoint}${subClass}${paging}${search}`;
const makeFetchRequest = async () => {
@@ -152,7 +153,7 @@ export const useITwinData = ({
accessToken,
requestType,
data,
- apiOverrides,
+ serverEnvironmentPrefix,
filterOptions,
page,
morePages,
diff --git a/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinFavorites.ts b/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinFavorites.ts
index 41e11ca8..5aab02ce 100644
--- a/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinFavorites.ts
+++ b/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinFavorites.ts
@@ -5,7 +5,6 @@
import { useCallback, useEffect, useState } from "react";
-import { ApiOverrides, ITwinFull } from "../../types";
import { _getAPIServer } from "../../utils/_apiOverrides";
const HOOK_ABORT_ERROR =
@@ -24,7 +23,7 @@ const HOOK_ABORT_ERROR =
*/
export const useITwinFavorites = (
accessToken: string | (() => Promise) | undefined,
- apiOverrides?: ApiOverrides
+ serverEnvironmentPrefix?: "dev" | "qa" | ""
): {
iTwinFavorites: Set;
addITwinToFavorites: (iTwinId: string) => Promise;
@@ -45,7 +44,9 @@ export const useITwinFavorites = (
if (!accessToken || !iTwinId || iTwinId === "") {
return;
}
- const url = `${_getAPIServer(apiOverrides)}/itwins/favorites/${iTwinId}`;
+ const url = `${_getAPIServer(
+ serverEnvironmentPrefix
+ )}/itwins/favorites/${iTwinId}`;
try {
const result = await fetch(url, {
method: "POST",
@@ -68,7 +69,7 @@ export const useITwinFavorites = (
console.error(error);
}
},
- [accessToken, apiOverrides]
+ [accessToken, serverEnvironmentPrefix]
);
/**
@@ -81,7 +82,9 @@ export const useITwinFavorites = (
if (!accessToken || !iTwinId || iTwinId === "") {
return;
}
- const url = `${_getAPIServer(apiOverrides)}/itwins/favorites/${iTwinId}`;
+ const url = `${_getAPIServer(
+ serverEnvironmentPrefix
+ )}/itwins/favorites/${iTwinId}`;
try {
const result = await fetch(url, {
method: "DELETE",
@@ -108,7 +111,7 @@ export const useITwinFavorites = (
console.error(error);
}
},
- [accessToken, apiOverrides]
+ [accessToken, serverEnvironmentPrefix]
);
/**
@@ -123,7 +126,7 @@ export const useITwinFavorites = (
return [];
}
const url = `${_getAPIServer(
- apiOverrides
+ serverEnvironmentPrefix
)}/itwins/favorites?subClass=Project`;
const result = await fetch(url, {
headers: {
@@ -152,7 +155,7 @@ export const useITwinFavorites = (
const response: ITwinFavoritesResponse = await result.json();
return response.iTwins;
},
- [accessToken, apiOverrides, shouldRefetchFavorites]
+ [accessToken, serverEnvironmentPrefix, shouldRefetchFavorites]
);
const resetShouldRefetchFavorites = useCallback(() => {
diff --git a/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinTableConfig.tsx b/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinTableConfig.tsx
index ad5d10e8..2994dbc9 100644
--- a/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinTableConfig.tsx
+++ b/packages/modules/imodel-browser/src/containers/ITwinGrid/useITwinTableConfig.tsx
@@ -139,6 +139,8 @@ export const useITwinTableConfig = ({
iTwinActions,
iTwinFavorites,
removeITwinFromFavorites,
+ strings.addToFavorites,
+ strings.removeFromFavorites,
strings.tableColumnDescription,
strings.tableColumnFavorites,
strings.tableColumnLastModified,
diff --git a/packages/modules/imodel-browser/src/containers/iModelGrid/IModelGrid.tsx b/packages/modules/imodel-browser/src/containers/iModelGrid/IModelGrid.tsx
index a164bffc..c7504935 100644
--- a/packages/modules/imodel-browser/src/containers/iModelGrid/IModelGrid.tsx
+++ b/packages/modules/imodel-browser/src/containers/iModelGrid/IModelGrid.tsx
@@ -25,7 +25,7 @@ import { useIModelData } from "./useIModelData";
import { useIModelTableConfig } from "./useIModelTableConfig";
export interface IModelGridProps {
/**
- * Access token that requires the `imodels:read` scope. Provide a function that returns the token to prevent the token from expiring. */
+ * Access token that requires the `imodels:read` scope. Must be memoized. Provide a function that returns the token to prevent the token from expiring. */
accessToken?: string | (() => Promise) | undefined;
/** ITwin Id to list the iModels from (mutually exclusive to assetId) */
iTwinId?: string | undefined;
diff --git a/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelData.ts b/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelData.ts
index 426573a4..fdc05b0a 100644
--- a/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelData.ts
+++ b/packages/modules/imodel-browser/src/containers/iModelGrid/useIModelData.ts
@@ -69,7 +69,7 @@ export const useIModelData = ({
accessToken,
iTwinId,
apiOverrides?.data,
- apiOverrides,
+ apiOverrides?.serverEnvironmentPrefix,
searchText,
maxCount,
]);
@@ -110,10 +110,9 @@ export const useIModelData = ({
const searching = searchText?.trim() ? `&$search=${searchText}` : "";
const abortController = new AbortController();
- const url = `${_getAPIServer({
- data: apiOverrides?.data,
- serverEnvironmentPrefix: apiOverrides?.serverEnvironmentPrefix,
- })}/imodels/${selection}${sorting}${paging}${searching}`;
+ const url = `${_getAPIServer(
+ apiOverrides?.serverEnvironmentPrefix
+ )}/imodels/${selection}${sorting}${paging}${searching}`;
const makeFetchRequest = async () => {
const options: RequestInit = {
diff --git a/packages/modules/imodel-browser/src/containers/iModelThumbnail/useIModelThumbnail.ts b/packages/modules/imodel-browser/src/containers/iModelThumbnail/useIModelThumbnail.ts
index 30c206cf..f82613e5 100644
--- a/packages/modules/imodel-browser/src/containers/iModelThumbnail/useIModelThumbnail.ts
+++ b/packages/modules/imodel-browser/src/containers/iModelThumbnail/useIModelThumbnail.ts
@@ -49,7 +49,9 @@ export const useIModelThumbnail = (
};
const response = await fetch(
- `${_getAPIServer(apiOverrides)}/imodels/${iModelId}/thumbnail`,
+ `${_getAPIServer(
+ apiOverrides?.serverEnvironmentPrefix
+ )}/imodels/${iModelId}/thumbnail`,
options
);
const thumbnail: string = response.ok
@@ -76,6 +78,12 @@ export const useIModelThumbnail = (
return () => {
abortController.abort();
};
- }, [accessToken, iModelId, thumbnail, apiOverrides?.data, apiOverrides]);
+ }, [
+ accessToken,
+ iModelId,
+ thumbnail,
+ apiOverrides?.data,
+ apiOverrides?.serverEnvironmentPrefix,
+ ]);
return thumbnail;
};
diff --git a/packages/modules/imodel-browser/src/utils/_apiOverrides.test.ts b/packages/modules/imodel-browser/src/utils/_apiOverrides.test.ts
index 6306b474..3799b1e2 100644
--- a/packages/modules/imodel-browser/src/utils/_apiOverrides.test.ts
+++ b/packages/modules/imodel-browser/src/utils/_apiOverrides.test.ts
@@ -6,17 +6,17 @@ import { _getAPIServer, _mergeStrings } from "./_apiOverrides";
describe("apiOverrides", () => {
describe("getAPIServer", () => {
- it("returns https://api.bentley.com with no serverEnvironmentPrefix", () => {
- const result = _getAPIServer({});
+ it("returns https://api.bentley.com with empty serverEnvironmentPrefix", () => {
+ const result = _getAPIServer("");
expect(result).toEqual("https://api.bentley.com");
});
it("returns https://[prefix]-api.bentley.com when provided with [prefix]", () => {
- const result = _getAPIServer({ serverEnvironmentPrefix: "qa" });
+ const result = _getAPIServer("qa");
expect(result).toEqual("https://qa-api.bentley.com");
});
- it("handles undefined overrdies", () => {
+ it("handles undefined overrides", () => {
const result = _getAPIServer(undefined);
expect(result).toEqual("https://api.bentley.com");
diff --git a/packages/modules/imodel-browser/src/utils/_apiOverrides.ts b/packages/modules/imodel-browser/src/utils/_apiOverrides.ts
index 2a706a18..60902814 100644
--- a/packages/modules/imodel-browser/src/utils/_apiOverrides.ts
+++ b/packages/modules/imodel-browser/src/utils/_apiOverrides.ts
@@ -2,18 +2,13 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
-import { ApiOverrides } from "../types";
/** Build APIM server url out of overrides
* @private
*/
-export const _getAPIServer = (
- apiOverrides: ApiOverrides | undefined
-) =>
+export const _getAPIServer = (serverEnvironmentPrefix?: "dev" | "qa" | "") =>
`https://${
- apiOverrides?.serverEnvironmentPrefix
- ? `${apiOverrides.serverEnvironmentPrefix}-`
- : ""
+ serverEnvironmentPrefix ? `${serverEnvironmentPrefix}-` : ""
}api.bentley.com`;
/**