Skip to content

Instantly share code, notes, and snippets.

@llamadeus
Created November 7, 2021 22:45
Show Gist options
  • Save llamadeus/ff8ffe7ac156e545575dad81142f1b6f to your computer and use it in GitHub Desktop.
Save llamadeus/ff8ffe7ac156e545575dad81142f1b6f to your computer and use it in GitHub Desktop.
Use React.Suspense for Apollo Client React hooks
import { gql, useQuery } from '@apollo/client';
import React, { ReactElement, useEffect, useMemo, useRef } from 'react';
const ME_QUERY = gql`
query Me {
me {
firstName
}
}
`;
function App() {
return (
<React.Suspense fallback={<p>loading...</p>}>
<Dashboard/>
</React.Suspense>
);
}
function Dashboard() {
const { data, loading } = useQuery(ME_QUERY);
if (loading) {
return <Suspender/>;
}
return (
<div>
<h1>Hello {data?.me?.firstName}</h1>
</div>
);
}
function Suspender(): ReactElement {
const resolve = useRef<() => void>();
const promise = useMemo(() => new Promise<void>((res) => {
resolve.current = res;
}), []);
useEffect(() => {
return () => {
resolve.current?.();
};
});
throw promise;
}
@s0ber
Copy link

s0ber commented Sep 7, 2022

In React v18 they cancel all effects if the tree is suspended, and Apollo triggers the query through effect. Following implementation of Suspender worked out for us:

import { useRef, useMemo, useState, useLayoutEffect } from 'react'

// The solution is copied from here with a small adjustment
// https://github.com/apollographql/apollo-feature-requests/issues/162#issuecomment-962696368
const Suspender = () => {
  const [isRenderedOnce, setIsRenderedOnce] = useState(false)
  const resolveRef = useRef<() => void>()
  const promise = useMemo(
    () =>
      new Promise<void>(resolve => {
        resolveRef.current = resolve
      }),
    []
  )

  useLayoutEffect(() => {
    setIsRenderedOnce(true)

    return () => {
      resolveRef.current?.()
    }
  }, [])

  if (isRenderedOnce) {
    throw promise
  }

  // React 18 doesn't run effects for suspended components.
  // We need to render at least once so that Apollo
  // will trigger a fetch request (which happens inside the effect).
  return null
}

export default Suspender

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment