Skip to content

Commit 069101b

Browse files
authored
feat(streaming): Make Cells render on the server with useBackgroundQuery and useReadQuery (#9074)
1 parent 4f2791a commit 069101b

15 files changed

+888
-462
lines changed

packages/web/jest.config.js

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ module.exports = {
88
'**/*.test.+(ts|tsx|js|jsx)',
99
'!**/__typetests__/*.+(ts|tsx|js|jsx)',
1010
],
11+
globals: {
12+
// Required for code that use experimental flags
13+
RWJS_ENV: {},
14+
},
1115
},
1216
{
1317
displayName: {

packages/web/src/apollo/index.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const {
1919
useQuery,
2020
useMutation,
2121
useSubscription,
22+
useBackgroundQuery,
23+
useReadQuery,
24+
useSuspenseQuery,
2225
setLogVerbosity: apolloSetLogVerbosity,
2326
} = apolloClient
2427

@@ -314,6 +317,9 @@ export const RedwoodApolloProvider: React.FunctionComponent<{
314317
useQuery={useQuery}
315318
useMutation={useMutation}
316319
useSubscription={useSubscription}
320+
useBackgroundQuery={useBackgroundQuery}
321+
useReadQuery={useReadQuery}
322+
useSuspenseQuery={useSuspenseQuery}
317323
>
318324
{children}
319325
</GraphQLHooksProvider>

packages/web/src/apollo/suspense.tsx

+9-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* This is a lift and shift of the original ApolloProvider
44
* but with suspense specific bits. Look for @MARK to find bits I've changed
55
*
6-
* Done this way, to avoid making changes breaking on main.
6+
* Done this way, to avoid making changes breaking on main, due to the experimental-nextjs import
7+
* Eventually we will have one ApolloProvider, not multiple.
78
*/
89

910
import type {
@@ -24,6 +25,9 @@ import {
2425
NextSSRApolloClient,
2526
NextSSRInMemoryCache,
2627
useSuspenseQuery,
28+
useBackgroundQuery,
29+
useReadQuery,
30+
useQuery,
2731
} from '@apollo/experimental-nextjs-app-support/ssr'
2832

2933
import { UseAuth, useNoAuth } from '@redwoodjs/auth'
@@ -229,12 +233,12 @@ export const RedwoodApolloProvider: React.FunctionComponent<{
229233
logLevel={logLevel}
230234
>
231235
<GraphQLHooksProvider
232-
// @MARK 👇 swapped useQuery for useSuspense query here
233-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
234-
// @ts-expect-error
235-
useQuery={useSuspenseQuery}
236+
useQuery={useQuery}
236237
useMutation={useMutation}
237238
useSubscription={useSubscription}
239+
useSuspenseQuery={useSuspenseQuery}
240+
useBackgroundQuery={useBackgroundQuery}
241+
useReadQuery={useReadQuery}
238242
>
239243
{children}
240244
</GraphQLHooksProvider>

packages/web/src/apollo/typeOverride.ts

+12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type {
77
OperationVariables,
88
SubscriptionHookOptions,
99
SubscriptionResult,
10+
UseSuspenseQueryResult,
11+
SuspenseQueryHookOptions,
1012
} from '@apollo/client'
1113

1214
// @MARK: Override relevant types from Apollo here
@@ -36,6 +38,16 @@ declare global {
3638
TData,
3739
TVariables extends OperationVariables
3840
> extends SubscriptionHookOptions<TData, TVariables> {}
41+
42+
interface SuspenseQueryOperationResult<
43+
TData = any,
44+
TVariables extends OperationVariables = OperationVariables
45+
> extends UseSuspenseQueryResult<TData, TVariables> {}
46+
47+
interface GraphQLSuspenseQueryHookOptions<
48+
TData,
49+
TVariables extends OperationVariables
50+
> extends SuspenseQueryHookOptions<TData, TVariables> {}
3951
}
4052

4153
export {}

packages/web/src/components/GraphQLHooksProvider.tsx

+89-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
1-
import { OperationVariables } from '@apollo/client'
1+
import type {
2+
OperationVariables,
3+
useBackgroundQuery as apolloUseBackgroundQuery,
4+
useReadQuery as apolloUseReadQuery,
5+
} from '@apollo/client'
26
import type { DocumentNode } from 'graphql'
37

8+
/**
9+
* @NOTE
10+
* The types QueryOperationResult, MutationOperationResult, SubscriptionOperationResult, and SuspenseQueryOperationResult
11+
* are overridden in packages/web/src/apollo/typeOverride.ts. This was originally so that you could bring your own gql client.
12+
*
13+
* The default (empty) types are defined in packages/web/src/global.web-auto-imports.ts
14+
*
15+
* Do not import types for hooks directly from Apollo here, unless it is an Apollo specific hook.
16+
*/
17+
418
type DefaultUseQueryType = <
519
TData = any,
620
TVariables extends OperationVariables = GraphQLOperationVariables
@@ -24,14 +38,29 @@ type DefaultUseSubscriptionType = <
2438
subscription: DocumentNode,
2539
options?: GraphQLSubscriptionHookOptions<TData, TVariables>
2640
) => SubscriptionOperationResult<TData, TVariables>
41+
42+
type DefaultUseSuspenseType = <
43+
TData = any,
44+
TVariables extends OperationVariables = GraphQLOperationVariables
45+
>(
46+
query: DocumentNode,
47+
options?: GraphQLSuspenseQueryHookOptions<TData, TVariables>
48+
) => SuspenseQueryOperationResult<TData, TVariables>
49+
2750
export interface GraphQLHooks<
2851
TuseQuery = DefaultUseQueryType,
2952
TuseMutation = DefaultUseMutationType,
30-
TuseSubscription = DefaultUseSubscriptionType
53+
TuseSubscription = DefaultUseSubscriptionType,
54+
TuseSuspenseQuery = DefaultUseSuspenseType
3155
> {
3256
useQuery: TuseQuery
3357
useMutation: TuseMutation
3458
useSubscription: TuseSubscription
59+
useSuspenseQuery: TuseSuspenseQuery
60+
// @NOTE note that we aren't using typeoverride here.
61+
// This is because useBackgroundQuery and useReadQuery are apollo specific hooks.
62+
useBackgroundQuery: typeof apolloUseBackgroundQuery
63+
useReadQuery: typeof apolloUseReadQuery
3564
}
3665

3766
export const GraphQLHooksContext = React.createContext<GraphQLHooks>({
@@ -50,13 +79,37 @@ export const GraphQLHooksContext = React.createContext<GraphQLHooks>({
5079
'You must register a useSubscription hook via the `GraphQLHooksProvider`'
5180
)
5281
},
82+
useSuspenseQuery: () => {
83+
throw new Error(
84+
'You must register a useSuspenseQuery hook via the `GraphQLHooksProvider`.'
85+
)
86+
},
87+
88+
// These are apollo specific hooks!
89+
useBackgroundQuery: () => {
90+
throw new Error(
91+
'You must register a useBackgroundQuery hook via the `GraphQLHooksProvider`.'
92+
)
93+
},
94+
95+
useReadQuery: () => {
96+
throw new Error(
97+
'You must register a useReadQuery hook via the `GraphQLHooksProvider`.'
98+
)
99+
},
53100
})
54101

55102
interface GraphQlHooksProviderProps<
56103
TuseQuery = DefaultUseQueryType,
57104
TuseMutation = DefaultUseMutationType,
58-
TuseSubscription = DefaultUseSubscriptionType
59-
> extends GraphQLHooks<TuseQuery, TuseMutation, TuseSubscription> {
105+
TuseSubscription = DefaultUseSubscriptionType,
106+
TuseSuspenseQuery = DefaultUseSuspenseType
107+
> extends GraphQLHooks<
108+
TuseQuery,
109+
TuseMutation,
110+
TuseSubscription,
111+
TuseSuspenseQuery
112+
> {
60113
children: React.ReactNode
61114
}
62115

@@ -74,6 +127,9 @@ export const GraphQLHooksProvider = <
74127
useQuery,
75128
useMutation,
76129
useSubscription,
130+
useSuspenseQuery,
131+
useBackgroundQuery,
132+
useReadQuery,
77133
children,
78134
}: GraphQlHooksProviderProps<TuseQuery, TuseMutation>) => {
79135
return (
@@ -82,6 +138,9 @@ export const GraphQLHooksProvider = <
82138
useQuery,
83139
useMutation,
84140
useSubscription,
141+
useSuspenseQuery,
142+
useBackgroundQuery,
143+
useReadQuery,
85144
}}
86145
>
87146
{children}
@@ -127,3 +186,29 @@ export function useSubscription<
127186
TVariables
128187
>(query, options)
129188
}
189+
190+
export function useSuspenseQuery<
191+
TData = any,
192+
TVariables extends OperationVariables = GraphQLOperationVariables
193+
>(
194+
query: DocumentNode,
195+
options?: GraphQLSuspenseQueryHookOptions<TData, TVariables>
196+
): SuspenseQueryOperationResult<TData, TVariables> {
197+
return React.useContext(GraphQLHooksContext).useSuspenseQuery<
198+
TData,
199+
TVariables
200+
>(query, options)
201+
}
202+
203+
export const useBackgroundQuery: typeof apolloUseBackgroundQuery<any> = (
204+
...args
205+
) => {
206+
// @TODO something about the apollo types here mean I need to override the return type
207+
return React.useContext(GraphQLHooksContext).useBackgroundQuery(
208+
...args
209+
) as any
210+
}
211+
212+
export const useReadQuery: typeof apolloUseReadQuery = (...args) => {
213+
return React.useContext(GraphQLHooksContext).useReadQuery(...args)
214+
}

packages/web/src/components/CellErrorBoundary.tsx packages/web/src/components/cell/CellErrorBoundary.tsx

+24-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import React from 'react'
22

3-
import type { CellFailureProps } from './createCell'
3+
import type { CellFailureProps } from './cellTypes'
44

5-
type CellErrorBoundaryProps = {
5+
export type FallbackProps = {
6+
error: QueryOperationResult['error']
7+
resetErrorBoundary: () => void
8+
}
9+
10+
export type CellErrorBoundaryProps = {
611
// Note that the fallback has to be an FC, not a Node
712
// because the error comes from this component's state
8-
fallback: React.FC<CellFailureProps>
13+
renderFallback: (
14+
fbProps: FallbackProps
15+
) => React.ReactElement<CellFailureProps>
916
children: React.ReactNode
1017
}
1118

@@ -29,21 +36,24 @@ export class CellErrorBoundary extends React.Component<
2936

3037
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
3138
// @TODO do something with this?
32-
console.log(error, errorInfo)
39+
console.log('Cell failure: ', {
40+
error,
41+
errorInfo,
42+
})
3343
}
3444

3545
render() {
36-
const { fallback: Fallback } = this.props
46+
// The fallback is constructed with all the props required, except error and errorCode
47+
// in createSusepndingCell.tsx
48+
const { renderFallback } = this.props
49+
3750
if (this.state.hasError) {
38-
return (
39-
<Fallback
40-
error={this.state.error}
41-
errorCode={
42-
this.state.error?.graphQLErrors?.[0]?.extensions?.['code'] as string
43-
}
44-
// @TODO (STREAMING) query-result not available here
45-
/>
46-
)
51+
return renderFallback({
52+
error: this.state.error,
53+
resetErrorBoundary: () => {
54+
this.setState({ hasError: false, error: undefined })
55+
},
56+
})
4757
}
4858

4959
return this.props.children

0 commit comments

Comments
 (0)