-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfilms.jsx
179 lines (165 loc) · 5.34 KB
/
films.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import { Fragment, Suspense } from 'react'
import Link from 'next/link'
import dynamic from 'next/dynamic'
import {
graphql,
useQueryLoader,
usePreloadedQuery,
useFragment,
fetchQuery,
} from 'react-relay'
import { initializeRelay, finalizeRelay } from '../lib/relay'
// There's a lot going on in this file. It can be broken down into 2 main
// sections, with their own data fetching strategies:
//
// 1. The data to load the initial page.
// This is the same as pages/index.jsx; using fragments & fetchQuery().
//
// 2. The data & component loaded when a button is clicked.
// a) We define a `LoadingCharacterTable` placeholder component for when
// we're loading the component and data.
// b) We tell Next.js that we'll dynamically import a `CharacterTable`
// component.
// c) The query `filmsCharacterQuery` uses a fragment defined in the
// `CharacterTable` component.
// d) `useQueryLoader()` gives us a `loadQuery()` method to lazily execute
// the query defined in 2. c)
// e) When the button is clicked, we start loading the dynamic component from
// 2. b), and also trigger the query defined in 2. c) by calling
// `loadQuery()` from 2. d). These two will run in parallel for maximum
// performance
// f) A <Suspense> boundary is rendered using the loading component in 2. a).
// g) `usePreloadedQuery()` Attempts to read the result of calling
// `loadQuery()` in 2. e). If the query hasn't completed yet, it will
// trigger the <Suspense> boundary from 2. f)
// h) Once 2. g) passes, React will attempt to render our dynamic component
// from 2. b). If it's still loading, it will render the loading component
// from 2. a).
export async function getStaticProps() {
const environment = initializeRelay()
const result = await fetchQuery(
environment,
graphql`
query filmsPageQuery {
allFilms(first: 10) {
edges {
node {
id
...films
}
}
}
}
`
).toPromise()
// Helper function to hydrate the Relay cache client side on page load
return finalizeRelay(environment, {
props: {
// Return the results directly so the component can render immediately
allFilms: result.allFilms,
},
revalidate: 1,
})
}
// 2. a)
const LoadingCharacterTable = () => 'Loading characters...'
// 2. b)
const CharacterTable = dynamic(
() =>
import('../components/character-table').then((mod) => mod.CharacterTable),
// NOTE: Can't use Next.js's suspense mode here; it will disable our ability
// to preload the component on button click.
// Instead, we use the same component here as we do for the Relay <Suspense
// fallback="..."> boundary.
{ loading: LoadingCharacterTable }
)
// 2. c)
const filmsCharacterQuery = graphql`
query filmsCharacterQuery($id: ID!) {
film(id: $id) {
...characterTable_film
}
}
`
const FilmCharacterTable = ({ queryReference }) => {
// 2. g)
const data = usePreloadedQuery(filmsCharacterQuery, queryReference)
// 2. h)
return <CharacterTable film={data.film} />
}
// This component is always rendered on this page, so it's inlined into this
// file. It could also be extracted into a separate file.
const Film = ({ film }) => {
const data = useFragment(
// Notice this fragment does _not_ include any character information. That
// will be loaded lazily when the button is clicked
graphql`
fragment films on Film {
# NOTE: We also request the 'id' field in the root query for this page.
# Relay is smart enough to dedupe this field for us, reducing the
# scope of maintenance to where the field is read.
id
title
episodeID
}
`,
film
)
// 2. d)
const [queryReference, loadQuery] = useQueryLoader(filmsCharacterQuery)
return (
<Fragment>
Episode {data.episodeID}: {data.title}
<br />
{queryReference == null ? (
<button
onClick={() => {
// 2. e)
// Start downloading the JS for the component (NOTE: This API is
// undocumented in Next.js)
CharacterTable.render.preload()
// In parallel, trigger the query
loadQuery({ id: data.id })
}}
disabled={queryReference != null}
>
Load Characters
</button>
) : (
// 2. f)
// NOTE: The fallback component here is the same as used for the dynamic
// component's `loading` prop
<Suspense fallback={<LoadingCharacterTable />}>
<FilmCharacterTable queryReference={queryReference} />
</Suspense>
)}
</Fragment>
)
}
const FilmsPage = ({ allFilms }) => (
<div>
<style jsx>{`
li {
margin-bottom: 1em;
}
`}</style>
<Link href="/">
<a>Home</a>
</Link>
|
<strong>Films</strong>
|
<Link href="/paginated">
<a>Paginated</a>
</Link>
<h1>StarWars Films</h1>
<ul>
{allFilms.edges.map(({ node: film }) => (
<li key={film.id}>
<Film film={film} />
</li>
))}
</ul>
</div>
)
export default FilmsPage