diff --git a/src/client.tsx b/src/client.tsx
index 16e208588..ffeba12e8 100644
--- a/src/client.tsx
+++ b/src/client.tsx
@@ -31,6 +31,7 @@ import "@fontsource/source-serif-pro/index.css";
import { i18nInstance } from "@ndla/ui";
import { getCookie, setCookie } from "@ndla/util";
import App from "./App";
+import GQLErrorContext, { ErrorContextInfo } from "./components/GQLErrorContext";
import ResponseContext, { ResponseInfo } from "./components/ResponseContext";
import { VersionHashProvider } from "./components/VersionHashContext";
import { STORED_LANGUAGE_COOKIE_KEY } from "./constants";
@@ -45,7 +46,7 @@ declare global {
}
const {
- DATA: { config, serverPath, serverQuery, serverResponse },
+ DATA: { config, serverPath, serverQuery, serverResponse, serverErrorContext },
} = window;
initSentry(config);
@@ -180,17 +181,20 @@ const renderOrHydrate = (container: HTMLElement, children: ReactNode) => {
}
};
const responseContext = new ResponseInfo(serverResponse);
+const errorContext = new ErrorContextInfo(serverErrorContext);
renderOrHydrate(
document.getElementById("root")!,
-
-
-
-
-
+
+
+
+
+
+
+
,
diff --git a/src/components/GQLErrorContext.tsx b/src/components/GQLErrorContext.tsx
new file mode 100644
index 000000000..c6ecb4c27
--- /dev/null
+++ b/src/components/GQLErrorContext.tsx
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2024-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { createContext, useContext } from "react";
+import { ApolloError, DocumentNode, OperationVariables, TypedDocumentNode } from "@apollo/client";
+import handleError from "../util/handleError";
+
+export interface ServerErrorContext {
+ error: string | undefined;
+}
+
+export class ErrorContextInfo {
+ error: { [key: string]: ApolloError };
+ constructor(serverErrorContext?: ServerErrorContext) {
+ this.error = serverErrorContext?.error ? JSON.parse(serverErrorContext.error) : {};
+ }
+
+ getError(key: string): ApolloError | undefined {
+ return this.error[key];
+ }
+
+ setError(key: string, error: ApolloError) {
+ this.error[key] = error;
+ }
+
+ serialize(): ServerErrorContext {
+ return { error: JSON.stringify(this.error) };
+ }
+}
+
+const GQLErrorContext = createContext(new ErrorContextInfo());
+
+export const useApolloErrors = (
+ errors: ApolloError | undefined,
+ query: DocumentNode | TypedDocumentNode,
+): ApolloError | undefined => {
+ const operation = query.definitions.find((definition) => definition.kind === "OperationDefinition");
+ const queryKey = operation?.name?.value;
+ const errorContext = useContext(GQLErrorContext);
+ if (!queryKey) {
+ handleError(new Error("No operation name found when using useApolloErrors, seems like a bug..."));
+ return errors;
+ }
+
+ if (errors) {
+ errorContext.setError(queryKey, errors);
+ return errors;
+ }
+
+ return errorContext.getError(queryKey);
+};
+export default GQLErrorContext;
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 22783631a..fe7171512 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -6,6 +6,7 @@
*
*/
import { NormalizedCacheObject } from "@apollo/client";
+import { ServerErrorContext } from "./components/GQLErrorContext";
import { ConfigType } from "./config";
import { LocaleValues } from "./constants";
@@ -30,6 +31,7 @@ export interface WindowData {
[key: string]: string | number | boolean | undefined | null;
};
serverResponse?: number;
+ serverErrorContext?: ServerErrorContext;
}
export interface NDLAWindow {
diff --git a/src/server/render/defaultRender.tsx b/src/server/render/defaultRender.tsx
index f4ef546c9..33dbc9152 100644
--- a/src/server/render/defaultRender.tsx
+++ b/src/server/render/defaultRender.tsx
@@ -15,6 +15,7 @@ import { i18nInstance } from "@ndla/ui";
import { getCookie } from "@ndla/util";
import { disableSSR } from "./renderHelpers";
import App from "../../App";
+import GQLErrorContext, { ErrorContextInfo, ServerErrorContext } from "../../components/GQLErrorContext";
import RedirectContext, { RedirectInfo } from "../../components/RedirectContext";
import ResponseContext, { ResponseInfo } from "../../components/ResponseContext";
import { VersionHashProvider } from "../../components/VersionHashContext";
@@ -70,6 +71,7 @@ export const defaultRender: RenderFunc = async (req) => {
const i18n = initializeI18n(i18nInstance, locale);
const redirectContext: RedirectInfo = {};
const responseContext: ResponseInfo = new ResponseInfo();
+ const errorContext = new ErrorContextInfo();
// @ts-ignore
const helmetContext: FilledContext = {};
@@ -78,15 +80,17 @@ export const defaultRender: RenderFunc = async (req) => {
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -104,6 +108,8 @@ export const defaultRender: RenderFunc = async (req) => {
const apolloState = client.extract();
+ const serverErrorContext: ServerErrorContext = errorContext.serialize();
+
return {
status: redirectContext.status ?? OK,
data: {
@@ -111,6 +117,7 @@ export const defaultRender: RenderFunc = async (req) => {
htmlContent: html,
data: {
serverResponse: redirectContext.status ?? undefined,
+ serverErrorContext,
serverPath: req.path,
serverQuery: req.query,
apolloState,
diff --git a/src/util/runQueries.ts b/src/util/runQueries.ts
index 452cbbf8f..6c14486de 100644
--- a/src/util/runQueries.ts
+++ b/src/util/runQueries.ts
@@ -13,14 +13,20 @@ import {
TypedDocumentNode,
useQuery,
} from "@apollo/client";
+import { useApolloErrors } from "../components/GQLErrorContext";
export function useGraphQuery(
query: DocumentNode | TypedDocumentNode,
options?: QueryHookOptions,
): QueryResult {
- return useQuery(query, {
+ const result = useQuery(query, {
errorPolicy: "all",
ssr: true,
...options,
});
+
+ // Apollo client does not cache errors, we need some way to pass errors to the client if they occur on the server side
+ const error = useApolloErrors(result.error, query);
+
+ return { ...result, error };
}