Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Syntax Error with ApolloNextAppProvider in Next.js App Router #385

Closed
eistin opened this issue Oct 31, 2024 · 1 comment
Closed

Syntax Error with ApolloNextAppProvider in Next.js App Router #385

eistin opened this issue Oct 31, 2024 · 1 comment

Comments

@eistin
Copy link

eistin commented Oct 31, 2024

Description

When implementing Apollo Client with Next.js App Router, I'm encountering a syntax error with the ApolloNextAppProvider component. The error suggests there's an issue with JSX syntax, specifically expecting a '>' character.

I tried to use the example from the README and add the authentication logic we have with our GraphQL API.

Current Code

export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}

Error Message

'>' expected.
Unterminated regular expression literal.
Declaration or statement expected.

Code

client-wrapper.ts
"use client";

import { Observable, from, FetchResult, Operation } from "@apollo/client";
import {
  ApolloNextAppProvider,
  ApolloClient,
  InMemoryCache,
} from "@apollo/experimental-nextjs-app-support";
import { onError } from "@apollo/client/link/error";
import { setContext } from "@apollo/client/link/context";
import { getAuth } from "firebase/auth";
import Cookies from "js-cookie";
import createUploadLink from "apollo-upload-client/createUploadLink.mjs";
import { PropsWithChildren } from "react";

// Types
type PendingRequest = () => void;

// Gestion du refresh token
let isRefreshing = false;
let pendingRequests: PendingRequest[] = [];

const resolvePendingRequests = () => {
  pendingRequests.forEach((callback) => callback());
  pendingRequests = [];
};

function makeClient() {
  // Upload link pour supporter les fichiers
  const httpLink = createUploadLink({
    uri: process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT + "/query",
    fetchOptions: { cache: "no-store" },
  });

  // Gestion de l'authentification
  const authLink = setContext(async (_, { headers }) => {
    const token = Cookies.get("auth_token");
    return {
      headers: {
        ...headers,
        "X-Atticard-Authorization": token ? `Bearer ${token}` : "",
      },
    };
  });

  // Gestion du refresh token
  const handleRefresh = (operation: Operation, forward: any) => {
    if (!isRefreshing) {
      isRefreshing = true;

      return new Observable<FetchResult>((observer) => {
        getAuth()
          .currentUser?.getIdToken(true)
          .then((newToken) => {
            Cookies.set("auth_token", newToken);
            operation.setContext(({ headers = {} }) => ({
              headers: {
                ...headers,
                "X-Atticard-Authorization": `Bearer ${newToken}`,
              },
            }));
          })
          .then(() => {
            isRefreshing = false;
            resolvePendingRequests();
            forward(operation).subscribe(observer);
          })
          .catch((error) => {
            console.error("Error refreshing auth token:", error);
            isRefreshing = false;
            pendingRequests = [];
            observer.error(error);
          });
      });
    } else {
      return new Observable<FetchResult>((observer) => {
        pendingRequests.push(() => {
          forward(operation).subscribe(observer);
        });
      });
    }
  };

  // Gestion des erreurs
  const errorLink = onError(
    ({ graphQLErrors, networkError, operation, forward }) => {
      if (
        networkError &&
        "statusCode" in networkError &&
        networkError.statusCode === 401
      ) {
        return handleRefresh(operation, forward);
      }
      if (graphQLErrors) {
        for (const err of graphQLErrors) {
          if (err.extensions?.code === "UNAUTHENTICATED") {
            return handleRefresh(operation, forward);
          }
        }
      }
    }
  );

  // Configuration du client Apollo
  return new ApolloClient({
    link: from([errorLink, authLink, httpLink]),
    cache: new InMemoryCache(),
    defaultOptions: {
      query: {
        fetchPolicy: "network-only",
      },
      watchQuery: {
        fetchPolicy: "network-only",
      },
    },
  });
}

// Provider component
export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}
package.json
{
  "name": "nextjs-webapp",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "dev-local": "next dev --experimental-https --hostname 0.0.0.0",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@apollo/client": "^3.11.8",
    "@apollo/experimental-nextjs-app-support": "^0.11.5",
    "@dnd-kit/core": "^6.1.0",
    "@dnd-kit/sortable": "^8.0.0",
    "@dnd-kit/utilities": "^3.2.2",
    "@radix-ui/react-avatar": "^1.1.0",
    "@radix-ui/react-dropdown-menu": "^2.1.1",
    "@radix-ui/react-label": "^2.1.0",
    "@radix-ui/react-select": "^2.1.1",
    "@radix-ui/react-separator": "^1.1.0",
    "@radix-ui/react-slot": "^1.1.0",
    "@radix-ui/react-switch": "^1.1.1",
    "@radix-ui/react-tabs": "^1.1.0",
    "@radix-ui/react-toast": "^1.2.2",
    "@stripe/react-stripe-js": "^2.8.1",
    "@stripe/stripe-js": "^1.0.0",
    "@types/js-cookie": "^3.0.6",
    "apollo-upload-client": "^18.0.1",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "firebase": "^10.13.2",
    "firebase-admin": "^12.5.0",
    "framer-motion": "^11.11.10",
    "graphql": "^16.9.0",
    "graphql-tag": "^2.12.6",
    "js-cookie": "^3.0.5",
    "lucide-react": "^0.441.0",
    "next": "^15.0.0",
    "next-themes": "^0.3.0",
    "react": "18.3.0",
    "react-dom": "18.3.0",
    "react-stripe-js": "^1.1.5",
    "shadcn": "^1.0.0",
    "sharp": "^0.33.5",
    "tailwind-merge": "^2.5.2",
    "tailwindcss-animate": "^1.0.7"
  },
  "devDependencies": {
    "@eslint/js": "^9.13.0",
    "@next/eslint-plugin-next": "^15.0.2",
    "@types/apollo-upload-client": "^18.0.0",
    "@types/node": "20.12.11",
    "@types/react": "18.3.1",
    "@types/react-dom": "18.3.0",
    "@typescript-eslint/eslint-plugin": "^8.12.2",
    "@typescript-eslint/parser": "^8.12.2",
    "eslint": "^9.13.0",
    "eslint-config-next": "^15.0.2",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}
tsconfig.json
{
  "compilerOptions": {
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": [
        "./src/*"
      ]
    },
    "target": "ES2017"
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

Screens :
Capture d’écran 2024-10-31 à 06 24 26
Capture d’écran 2024-10-31 à 06 25 11

@eistin eistin closed this as completed Oct 31, 2024
Copy link

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant