Skip to content
New issue

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

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

Already on GitHub? Sign in 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
Contributor

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.

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

No branches or pull requests

1 participant