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

Intermittent missing client during client redirects #305

Closed
awinograd opened this issue May 28, 2024 · 8 comments
Closed

Intermittent missing client during client redirects #305

awinograd opened this issue May 28, 2024 · 8 comments

Comments

@awinograd
Copy link

awinograd commented May 28, 2024

I'm seeing an error in our e2e tests on CI that I cannot reproduce locally. This issue is flaky, but is happening fairly often. I have a component that will throw the following invariant error:

Could not find "client" in the context or passed in as an option. Wrap the root component in an , or pass an ApolloClient instance in via options.

https://www.apollographql.com/docs/react/errors#%7B%22version%22%3A%223.10.4%22%2C%22message%22%3A49%2C%22args%22%3A%5B%5D%7D

This appears to occur only in tests that call redirect() in client components conditionally based off the render of a query. The error is thrown in a separate component in the layout that has it's own query. I'm struggling to create a MRE since i cannot even re-create the issue in the same code locally, but pseudo-code is something like the following.

I'd be grateful for any guidance on how to capture more information / anything I can try to get to the bottom of the issue! Thank you!

// app/layout.tsx
export function RootLayout() {
  return <ApolloWrapper>{children}</ApolloWrapper>;
}

// ApolloWrapper.tsx
'use client'
export function ApolloWrapper() {
  const [client] = useState(() =>
    createApolloClientBase({
      /*...*/
    }),
  );

  return (
    <ApolloProvider makeClient={() => client}>
      {children}
    </ApolloProvider>
  );
}

// app/dashboard/layout.tsx
export function DashboardLayout() {
  return (
    <div>
      <Header />
      {children}
    </div>
  );
}

// app/dashboard/main/page.tsx
'use client'
export function MainPage() {
  const {data} = useQuery(QueryA);

  if (data.foo) redirect('/dashboard/secondary')

  return (
    <div>
    </div>
  );
}

// app/dashboard/secondary/page.tsx
'use client'
export function SecondaryPage() {
  const {data} = useQuery(QueryB);
  return (
    <div>
    </div>
  );
}

// Header.tsx
'use client'

// This component renders on the initial page load and queries data successfully. It appears to throw around the time of the redirect from /dashboard/main -> /dashboard/secondary
export function Header() {
  const {data} = useQuery(HeaderQuery);
  // Could not find "client" in the context or passed in as an option. Wrap the root component in an <ApolloProvider>, or pass an ApolloClient instance in via options.
  return (
    <div>
    </div>
  );
}

Versions:
@apollo/client@npm:3.10.4
@apollo/experimental-nextjs-app-support@npm:0.10.1

@phryneas
Copy link
Member

One thing: that useState there is definitely not doing you any good. We hold a state like this internally, so that only adds indirection.

Could you please replace that with

'use client'
export function ApolloWrapper() {
  return (
    <ApolloProvider makeClient={() =>
    createApolloClientBase({
      /*...*/
    })}>
      {children}
    </ApolloProvider>
  );
}

just so we have a better place to start from?


That said, to me that error looks like it is coming from a component that is somewhere outside of your ApolloProvider. Either that, or in a completely different React render context - are you maybe using something like react-skia or react-three-fiber somewhere?

@awinograd
Copy link
Author

I will work on removal of the useState... it's not super straightforward because we do use the client elsewhere in hooks in the component but should be possible.

My understanding of nextjs layouts is that everything is inside the root layout.tsx. We aren't using any libraries that would render outside of the main react tree. I'm also not sure how Header could work 99% of the time and only fail intermittently if it is in a different render context. That's the weirdest thing to me... I would imagine it's either a child/grandchild of the provider or not 🤷

@phryneas
Copy link
Member

Could you split the layout into two components? One wrapper that just contains the Provider, and then your code in a child class that calls useApolloStore?

That way we could also rule out any components outside the tree.

Generally, I have to say I'm sorry, but I'm not sure how I can help you without a reproduction. 😞 I have never seen this happen and don't know where I could even start looking for it, I'm really sorry!

@awinograd
Copy link
Author

@phryneas thanks for your thoughts / ideas. I haven't had time to apply your suggestions yet today, but will give it a go soon.

I understand the lack of reproduction makes this difficult to offer guidance on! I will report back if I discover anything but feel free to close out this issue if you'd like and thanks again!

@awinograd
Copy link
Author

Hi @phryneas ,

Fortunately, It looks like this issue is resolved for us and I wanted report back what I've learned. Unfortunately it's nothing super concrete but hopefully will be useful context for someone in the future.

I was not able to 100% identify the root cause. What I ended up doing was:

  1. change usage of useQuery to useSuspenseQuery on the offending pages / components
  2. After doing so, I started seeing a consistent SSR error being thrown due to calling new Image() in a component (a global that only exists on the client)
  3. I moved the usage of Image behind a useEffect

We haven't seen the invariant error thrown in the past day or so since pushing this to our main branch. It was always intermittent but I would have expected to see it by now so hopefully that means it's resolved.

My only guess as to what happened (and maybe this makes no sense) is that there is some race condition with network loading where before the fix, sometimes new Image would be called on the server and sometimes not. When called on the server, nextjs tried to render an internal error boundary (we dont have an explicit one in our app dir) that doesn't have access to the provider.

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.

@awinograd
Copy link
Author

Forgot to add:

Thanks again for your input & time on this issue. While you didn't have a concrete suggestion, that was still helpful guidance in understanding I wasn't totally crazy in terms of our implementation :)

@phryneas
Copy link
Member

Great to hear you solved the issue - thanks for keeping me updated :)

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

2 participants