diff --git a/frontend/src/App/App.tsx b/frontend/src/App/App.tsx
index 166bf5dec5..c3443eb6ea 100644
--- a/frontend/src/App/App.tsx
+++ b/frontend/src/App/App.tsx
@@ -1,3 +1,4 @@
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ConnectedRouter, ConnectedRouterProps } from 'connected-react-router';
import React from 'react';
import DocumentTitle from 'react-document-title';
@@ -12,17 +13,21 @@ interface AppProps {
history: ConnectedRouterProps['history'];
}
+const queryClient = new QueryClient();
+
function App({ store, history }: AppProps) {
return (
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/frontend/src/Helpers/Hooks/useApiQuery.ts b/frontend/src/Helpers/Hooks/useApiQuery.ts
new file mode 100644
index 0000000000..5120e42212
--- /dev/null
+++ b/frontend/src/Helpers/Hooks/useApiQuery.ts
@@ -0,0 +1,56 @@
+import { useQuery } from '@tanstack/react-query';
+import { useMemo } from 'react';
+
+interface QueryOptions {
+ url: string;
+ headers?: HeadersInit;
+}
+
+const absUrlRegex = /^(https?:)?\/\//i;
+const apiRoot = window.Radarr.apiRoot;
+
+function isAbsolute(url: string) {
+ return absUrlRegex.test(url);
+}
+
+function getUrl(url: string) {
+ return apiRoot + url;
+}
+
+function useApiQuery(options: QueryOptions) {
+ const { url, headers } = options;
+
+ const final = useMemo(() => {
+ if (isAbsolute(url)) {
+ return {
+ url,
+ headers,
+ };
+ }
+
+ return {
+ url: getUrl(url),
+ headers: {
+ ...headers,
+ 'X-Api-Key': window.Radarr.apiKey,
+ },
+ };
+ }, [url, headers]);
+
+ return useQuery({
+ queryKey: [final.url],
+ queryFn: async () => {
+ const result = await fetch(final.url, {
+ headers: final.headers,
+ });
+
+ if (!result.ok) {
+ throw new Error('Failed to fetch');
+ }
+
+ return result.json() as T;
+ },
+ });
+}
+
+export default useApiQuery;
diff --git a/frontend/typings/Globals.d.ts b/frontend/typings/Globals.d.ts
index 21e40447bd..51ba66e950 100644
--- a/frontend/typings/Globals.d.ts
+++ b/frontend/typings/Globals.d.ts
@@ -3,6 +3,7 @@ declare module '*.module.css';
interface Window {
Radarr: {
apiKey: string;
+ apiRoot: string;
instanceName: string;
theme: string;
urlBase: string;
diff --git a/package.json b/package.json
index 2ab9c29268..9b410fe0c6 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"@microsoft/signalr": "6.0.25",
"@sentry/browser": "7.119.1",
"@sentry/integrations": "7.119.1",
+ "@tanstack/react-query": "5.74.3",
"@types/node": "20.16.11",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
diff --git a/yarn.lock b/yarn.lock
index 9e304e216e..3865736aea 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1295,6 +1295,18 @@
dependencies:
"@sentry/types" "7.119.1"
+"@tanstack/query-core@5.74.3":
+ version "5.74.3"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.74.3.tgz#1fc97bd9a47f2acdf9f49737b1e6969e7bbcb7d7"
+ integrity sha512-Mqk+5o3qTuAiZML248XpNH8r2cOzl15+LTbUsZQEwvSvn1GU4VQhvqzAbil36p+MBxpr/58oBSnRzhrBevDhfg==
+
+"@tanstack/react-query@5.74.3":
+ version "5.74.3"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.74.3.tgz#f7acd825abaea091f009d1c3f115212e45c4ee74"
+ integrity sha512-QrycUn0wxjVPzITvQvOxFRdhlAwIoOQSuav7qWD4SWCoKCdLbyRZ2vji2GuBq/glaxbF4wBx3fqcYRDOt8KDTA==
+ dependencies:
+ "@tanstack/query-core" "5.74.3"
+
"@types/archiver@^5.3.1":
version "5.3.4"
resolved "https://registry.yarnpkg.com/@types/archiver/-/archiver-5.3.4.tgz#32172d5a56f165b5b4ac902e366248bf03d3ae84"