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

With React Query: Invariant: headers() expects to have requestAsyncStorage, none available. #1649

Open
1 task
borzaka opened this issue Apr 29, 2024 · 3 comments
Labels
bug Something isn't working openapi-fetch Relevant to the openapi-fetch library

Comments

@borzaka
Copy link

borzaka commented Apr 29, 2024

Description

My stack:

  • Next.js (App Router)
  • Auth.js
  • openapi-typescript
  • openapi-fetch
  • TanStack Query (React Query)

Reproduction

  • On the server side, the openapi-fetch works great:
    const settingsResponse = await client.GET("/api/settings/config-properties");
  • On the client side, it throws an error:
Error: Invariant: headers() expects to have requestAsyncStorage, none available.
    at headers (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/headers.js:38:15)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/next-auth/lib/index.js:97:84)
    at Object.onRequest (webpack-internal:///(app-pages-browser)/./src/lib/api/index.ts:11:78)
    at coreFetch (webpack-internal:///(app-pages-browser)/./node_modules/openapi-fetch/dist/index.js:100:32)
    at Object.GET (webpack-internal:///(app-pages-browser)/./node_modules/openapi-fetch/dist/index.js:163:14)
    at Object.queryFn (webpack-internal:///(app-pages-browser)/./src/components/project-card-list.tsx:38:83)
    at Object.fetchFn [as fn] (webpack-internal:///(app-pages-browser)/./node_modules/@tanstack/query-core/build/modern/query.js:195:27)
    at run (webpack-internal:///(app-pages-browser)/./node_modules/@tanstack/query-core/build/modern/retryer.js:92:31)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/@tanstack/query-core/build/modern/retryer.js:116:11)

My client component (components/project-card-list.tsx):

"use client";
...
function ProjectCardList() {
  const { isPending, error, data, isFetching } = useQuery({
    queryKey: ["getConfigProperties"],
    queryFn: async () => {
      const { data } = await client.GET("/api/settings/config-properties");
      return data;
    },
  });

  return (
    <div>
      {isPending && <div>Loading...</div>}
      {error ? (
        <div>There was an error: {error.message}</div>
      ) : (
        <pre>
          <code>{JSON.stringify(data, undefined, 2)}</code>
        </pre>
      )}
    </div>
  );
}

export default ProjectCardList;

My openapi-fetch client with Middleware (lib/api/index.ts):

import createClient, { type Middleware } from "openapi-fetch";

import { auth } from "@/auth";

import type { paths } from "./api-docs";

let accessToken: string | undefined = undefined;

const authMiddleware: Middleware = {
  async onRequest(request) {
    if (!accessToken) {
      const session = await auth();

      if (!session?.user.accessToken) {
        return undefined;
      } else {
        accessToken = session.user.accessToken;
      }
    }

    request.headers.set("Authorization", `Bearer ${accessToken}`);
    return request;
  },
  async onResponse(response) {
    if (response.status >= 400) {
      const body = response.headers.get("content-type")?.includes("json")
        ? await response.clone().json()
        : await response.clone().text();
      throw new Error(body);
    }
    return undefined;
  },
};

const client = createClient<paths>({
  baseUrl: process.env.API_BASE_URL,
  headers: {
    Accept: "application/json",
  },
});
client.use(authMiddleware);

export default client;

I have followed the examples here for Next.js App Router to create the QueryClientProvider:
https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr

It works fine with native fetch:

const { data, status } = useQuery({
  queryKey: ["episodes"],
  queryFn: async () => {
    return await fetch("https://rickandmortyapi.com/api/episode").then(
      (response) => response.json(),
    );
  },
});

What am I missing?

Expected result

Work as intended in client component with React Query.

Checklist

@borzaka borzaka added bug Something isn't working openapi-fetch Relevant to the openapi-fetch library labels Apr 29, 2024
@borzaka
Copy link
Author

borzaka commented Apr 30, 2024

I have figured out. The useQuery's queryFn needs to be on the server side.

actions.ts:

"use server";

export const fetchSettings = async () => {
  const { data } = await client.GET("/api/settings/config-properties");
  return data;
};

My client component:

"use client";

import { fetchSettings } from "@/lib/actions";
import { useQuery } from "@tanstack/react-query";

const { isPending, error, data, isFetching } = useQuery({
  queryKey: ["getConfigProperties"],
  queryFn: async () => fetchSettings(),
});

I don't know why needed like this in my case, but maybe worth a note somewhere in the docs.
Hope it helps someone.

@drwpow
Copy link
Owner

drwpow commented Apr 30, 2024

@borzaka That’s a good find! Maybe this would be a good addition to examples/next (since the React Query example is currently only client-side)? Or even a new examples/next-react-query would be great as well if you’re able to provide it

@borzaka
Copy link
Author

borzaka commented May 1, 2024

  • The Next.js example is on the server side, so no problem there with openapi-typescript alone without React Query.
  • The React Query example is on the client side, but I have no idea why works there.

Every other example I found with Next.js and React Query, the queryFn was on the server side. Like in this:
https://github.com/developedbyed/next14-query-combo-cache-destroyer/tree/master

app/page.tsx

import PostForm from "@/components/post-form"
import Posts from "@/components/posts"
import { fetchPosts } from "@/server/actions/create-post"
import {
  QueryClient,
  HydrationBoundary,
  dehydrate,
} from "@tanstack/react-query"

export default async function Home() {
  const queryClient = new QueryClient()

  await queryClient.prefetchQuery({
    queryKey: ["posts"],
    queryFn: fetchPosts,
  })
  return (
    <main>
      <HydrationBoundary state={dehydrate(queryClient)}>
        <PostForm />
        <Posts />
      </HydrationBoundary>
    </main>
  )
}

server/actions/create-post.ts

"use server";

export const fetchPosts = async () => {
  const posts = await db.query.posts.findMany({
    with: {
      author: true,
      likes: true,
    },
    orderBy: (posts, { desc }) => [desc(posts.timestamp)],
  })
  if (!posts) return { error: "No posts 😓" }
  if (posts) return { success: posts }
}

And I also have problem to create type interface, to pass down props to the server side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working openapi-fetch Relevant to the openapi-fetch library
Projects
None yet
Development

No branches or pull requests

2 participants