Skip to content

Commit 816d355

Browse files
authored
Suspense examples (#104)
1 parent 3fa08c4 commit 816d355

13 files changed

+718
-31
lines changed

apollo-client/v3/suspense-hooks/package-lock.json

+51
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apollo-client/v3/suspense-hooks/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"graphql": "16.7.1",
1515
"react": "18.2.0",
1616
"react-dom": "18.2.0",
17+
"react-error-boundary": "4.0.10",
18+
"react-router-dom": "^6.14.2",
1719
"react-scripts": "5.0.1",
1820
"typescript": "5.1.6",
1921
"web-vitals": "3.4.0"

apollo-client/v3/suspense-hooks/src/index.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
body {
2-
margin: 0;
2+
margin: 1rem;
33
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
44
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
55
sans-serif;
+15-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import React from "react";
2+
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
23
import ReactDOM from "react-dom/client";
34
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
4-
import "./index.css";
5-
import App from "./App";
65
import { link } from "./schema";
6+
import { Layout } from "./layout";
7+
import { routes } from "./routes";
8+
9+
import "./index.css";
710

811
const client = new ApolloClient({
912
cache: new InMemoryCache(),
@@ -13,10 +16,19 @@ const client = new ApolloClient({
1316
const root = ReactDOM.createRoot(
1417
document.getElementById("root") as HTMLElement
1518
);
19+
1620
root.render(
1721
<React.StrictMode>
1822
<ApolloProvider client={client}>
19-
<App />
23+
<Router>
24+
<Routes>
25+
<Route path="/" element={<Layout />}>
26+
{routes.map(({ path, Element }) => (
27+
<Route key={path} path={path} element={<Element />} />
28+
))}
29+
</Route>
30+
</Routes>
31+
</Router>
2032
</ApolloProvider>
2133
</React.StrictMode>
2234
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Outlet, useLocation } from "react-router-dom";
2+
import { routes } from "./routes";
3+
import { useMemo } from "react";
4+
5+
export function Layout() {
6+
const { pathname } = useLocation();
7+
const title = useMemo(
8+
() => routes.find((route) => `/${route.path}` === pathname)?.title || "",
9+
[pathname]
10+
);
11+
12+
return (
13+
<div className="App">
14+
<header className="App-header">
15+
<h1>Apollo Client Suspense Hook Examples</h1>
16+
<p>
17+
This application contains the code samples that appear in{" "}
18+
<a
19+
target="_blank"
20+
rel="noreferrer"
21+
href="https://www.apollographql.com/docs/react/data/suspense"
22+
>
23+
Apollo Client's Suspense hooks documentation
24+
</a>
25+
.
26+
</p>
27+
28+
<nav>
29+
<ul>
30+
{routes.map(({ path, title }) => (
31+
<li key={path}>
32+
<a
33+
href={`/${path}`}
34+
dangerouslySetInnerHTML={{ __html: title }}
35+
/>
36+
</li>
37+
))}
38+
</ul>
39+
</nav>
40+
</header>
41+
<div className="Grid-column">
42+
<h2 dangerouslySetInnerHTML={{ __html: title }} />
43+
<Outlet />
44+
</div>
45+
</div>
46+
);
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { Suspense, useTransition } from "react";
2+
import {
3+
gql,
4+
TypedDocumentNode,
5+
useBackgroundQuery,
6+
useSuspenseQuery,
7+
QueryReference,
8+
useReadQuery,
9+
} from "@apollo/client";
10+
11+
interface Data {
12+
dog: {
13+
id: string;
14+
name: string;
15+
breed: string;
16+
};
17+
}
18+
19+
interface BreedData {
20+
breeds: { id: string; name: string; characteristics: string[] }[];
21+
}
22+
23+
interface Variables {
24+
id: string;
25+
}
26+
27+
interface DogProps {
28+
id: string;
29+
isPending: boolean;
30+
queryRef: QueryReference<BreedData>;
31+
refetchHandler: () => void;
32+
}
33+
34+
interface BreedsProps {
35+
isPending: boolean;
36+
queryRef: QueryReference<BreedData>;
37+
}
38+
39+
export const GET_DOG_QUERY: TypedDocumentNode<Data, Variables> = gql`
40+
query GetDog($id: String) {
41+
dog(id: $id) {
42+
# By default, an object's cache key is a combination of its
43+
# __typename and id fields, so we should always make sure the
44+
# id is in the response so our data can be normalized and cached properly.
45+
id
46+
name
47+
breed
48+
}
49+
}
50+
`;
51+
52+
export const GET_BREEDS_QUERY: TypedDocumentNode<BreedData> = gql`
53+
query GetBreeds {
54+
breeds {
55+
id
56+
name
57+
characteristics
58+
}
59+
}
60+
`;
61+
62+
function App() {
63+
const [isPending, startTransition] = useTransition();
64+
const [queryRef, { refetch }] = useBackgroundQuery(GET_BREEDS_QUERY);
65+
66+
const refetchHandler = () => {
67+
startTransition(() => {
68+
refetch();
69+
});
70+
};
71+
72+
return (
73+
<Suspense fallback={<div>Loading...</div>}>
74+
<Dog
75+
id="3"
76+
queryRef={queryRef}
77+
isPending={isPending}
78+
refetchHandler={refetchHandler}
79+
/>
80+
</Suspense>
81+
);
82+
}
83+
84+
function Dog({ id, queryRef, isPending, refetchHandler }: DogProps) {
85+
const { data } = useSuspenseQuery(GET_DOG_QUERY, {
86+
variables: { id },
87+
});
88+
89+
return (
90+
<>
91+
Name: {data.dog.name}
92+
<Suspense fallback={<div>Loading breeds...</div>}>
93+
<Breeds isPending={isPending} queryRef={queryRef} />
94+
</Suspense>
95+
<button onClick={refetchHandler}>Refetch!</button>
96+
</>
97+
);
98+
}
99+
100+
function Breeds({ queryRef, isPending }: BreedsProps) {
101+
const { data } = useReadQuery(queryRef);
102+
return data.breeds.map(({ characteristics }) =>
103+
characteristics.map((characteristic) => (
104+
<div style={{ opacity: `${isPending ? 0.5 : 1}` }} key={characteristic}>
105+
{characteristic}
106+
</div>
107+
))
108+
);
109+
}
110+
111+
export default App;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Pages
2+
import UseSuspenseQuery from "./useSuspenseQuery";
3+
import UseSuspenseQueryChangingVariables from "./useSuspenseQuery-changing-variables";
4+
import UseSuspenseQueryPartialData from "./useSuspenseQuery-partialData";
5+
import UseSuspenseQueryErrorHandling from "./useSuspsenseQuery-error-handling";
6+
import UseBackgroundQuery from "./useBackgroundQuery";
7+
import Refetch from "./refetch-fetchMore";
8+
9+
export const routes = [
10+
{
11+
path: "useSuspenseQuery",
12+
title: "Fetching with <code>useSuspenseQuery</code>",
13+
Element: UseSuspenseQuery,
14+
},
15+
{
16+
path: "useSuspenseQuery-changing-variables",
17+
title: "<code>useSuspenseQuery</code>: changing variables",
18+
Element: UseSuspenseQueryChangingVariables,
19+
},
20+
{
21+
path: "useSuspenseQuery-partial-data",
22+
title: "<code>useSuspenseQuery</code>: rendering partial data",
23+
Element: UseSuspenseQueryPartialData,
24+
},
25+
{
26+
path: "useSuspenseQuery-error-handling",
27+
title: "<code>useSuspenseQuery</code>: error handling",
28+
Element: UseSuspenseQueryErrorHandling,
29+
},
30+
{
31+
path: "useBackgroundQuery-useReadQuery",
32+
title:
33+
"Fetching with <code>useBackgroundQuery</code> and rendering with <code>useReadQuery</code>",
34+
Element: UseBackgroundQuery,
35+
},
36+
{
37+
path: "refetch",
38+
title:
39+
"Refetching with <code>useSuspenseQuery</code> and <code>useBackgroundQuery</code>",
40+
Element: Refetch,
41+
},
42+
];

0 commit comments

Comments
 (0)