Skip to content

Commit

Permalink
feat(streaming): Make Cells render on the server with useBackgroundQu…
Browse files Browse the repository at this point in the history
…ery and useReadQuery (#9074)
  • Loading branch information
dac09 authored Aug 30, 2023
1 parent 4f2791a commit 069101b
Show file tree
Hide file tree
Showing 15 changed files with 888 additions and 462 deletions.
4 changes: 4 additions & 0 deletions packages/web/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ module.exports = {
'**/*.test.+(ts|tsx|js|jsx)',
'!**/__typetests__/*.+(ts|tsx|js|jsx)',
],
globals: {
// Required for code that use experimental flags
RWJS_ENV: {},
},
},
{
displayName: {
Expand Down
6 changes: 6 additions & 0 deletions packages/web/src/apollo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const {
useQuery,
useMutation,
useSubscription,
useBackgroundQuery,
useReadQuery,
useSuspenseQuery,
setLogVerbosity: apolloSetLogVerbosity,
} = apolloClient

Expand Down Expand Up @@ -314,6 +317,9 @@ export const RedwoodApolloProvider: React.FunctionComponent<{
useQuery={useQuery}
useMutation={useMutation}
useSubscription={useSubscription}
useBackgroundQuery={useBackgroundQuery}
useReadQuery={useReadQuery}
useSuspenseQuery={useSuspenseQuery}
>
{children}
</GraphQLHooksProvider>
Expand Down
14 changes: 9 additions & 5 deletions packages/web/src/apollo/suspense.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* This is a lift and shift of the original ApolloProvider
* but with suspense specific bits. Look for @MARK to find bits I've changed
*
* Done this way, to avoid making changes breaking on main.
* Done this way, to avoid making changes breaking on main, due to the experimental-nextjs import
* Eventually we will have one ApolloProvider, not multiple.
*/

import type {
Expand All @@ -24,6 +25,9 @@ import {
NextSSRApolloClient,
NextSSRInMemoryCache,
useSuspenseQuery,
useBackgroundQuery,
useReadQuery,
useQuery,
} from '@apollo/experimental-nextjs-app-support/ssr'

import { UseAuth, useNoAuth } from '@redwoodjs/auth'
Expand Down Expand Up @@ -229,12 +233,12 @@ export const RedwoodApolloProvider: React.FunctionComponent<{
logLevel={logLevel}
>
<GraphQLHooksProvider
// @MARK 👇 swapped useQuery for useSuspense query here
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
useQuery={useSuspenseQuery}
useQuery={useQuery}
useMutation={useMutation}
useSubscription={useSubscription}
useSuspenseQuery={useSuspenseQuery}
useBackgroundQuery={useBackgroundQuery}
useReadQuery={useReadQuery}
>
{children}
</GraphQLHooksProvider>
Expand Down
12 changes: 12 additions & 0 deletions packages/web/src/apollo/typeOverride.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import type {
OperationVariables,
SubscriptionHookOptions,
SubscriptionResult,
UseSuspenseQueryResult,
SuspenseQueryHookOptions,
} from '@apollo/client'

// @MARK: Override relevant types from Apollo here
Expand Down Expand Up @@ -36,6 +38,16 @@ declare global {
TData,
TVariables extends OperationVariables
> extends SubscriptionHookOptions<TData, TVariables> {}

interface SuspenseQueryOperationResult<
TData = any,
TVariables extends OperationVariables = OperationVariables
> extends UseSuspenseQueryResult<TData, TVariables> {}

interface GraphQLSuspenseQueryHookOptions<
TData,
TVariables extends OperationVariables
> extends SuspenseQueryHookOptions<TData, TVariables> {}
}

export {}
93 changes: 89 additions & 4 deletions packages/web/src/components/GraphQLHooksProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import { OperationVariables } from '@apollo/client'
import type {
OperationVariables,
useBackgroundQuery as apolloUseBackgroundQuery,
useReadQuery as apolloUseReadQuery,
} from '@apollo/client'
import type { DocumentNode } from 'graphql'

/**
* @NOTE
* The types QueryOperationResult, MutationOperationResult, SubscriptionOperationResult, and SuspenseQueryOperationResult
* are overridden in packages/web/src/apollo/typeOverride.ts. This was originally so that you could bring your own gql client.
*
* The default (empty) types are defined in packages/web/src/global.web-auto-imports.ts
*
* Do not import types for hooks directly from Apollo here, unless it is an Apollo specific hook.
*/

type DefaultUseQueryType = <
TData = any,
TVariables extends OperationVariables = GraphQLOperationVariables
Expand All @@ -24,14 +38,29 @@ type DefaultUseSubscriptionType = <
subscription: DocumentNode,
options?: GraphQLSubscriptionHookOptions<TData, TVariables>
) => SubscriptionOperationResult<TData, TVariables>

type DefaultUseSuspenseType = <
TData = any,
TVariables extends OperationVariables = GraphQLOperationVariables
>(
query: DocumentNode,
options?: GraphQLSuspenseQueryHookOptions<TData, TVariables>
) => SuspenseQueryOperationResult<TData, TVariables>

export interface GraphQLHooks<
TuseQuery = DefaultUseQueryType,
TuseMutation = DefaultUseMutationType,
TuseSubscription = DefaultUseSubscriptionType
TuseSubscription = DefaultUseSubscriptionType,
TuseSuspenseQuery = DefaultUseSuspenseType
> {
useQuery: TuseQuery
useMutation: TuseMutation
useSubscription: TuseSubscription
useSuspenseQuery: TuseSuspenseQuery
// @NOTE note that we aren't using typeoverride here.
// This is because useBackgroundQuery and useReadQuery are apollo specific hooks.
useBackgroundQuery: typeof apolloUseBackgroundQuery
useReadQuery: typeof apolloUseReadQuery
}

export const GraphQLHooksContext = React.createContext<GraphQLHooks>({
Expand All @@ -50,13 +79,37 @@ export const GraphQLHooksContext = React.createContext<GraphQLHooks>({
'You must register a useSubscription hook via the `GraphQLHooksProvider`'
)
},
useSuspenseQuery: () => {
throw new Error(
'You must register a useSuspenseQuery hook via the `GraphQLHooksProvider`.'
)
},

// These are apollo specific hooks!
useBackgroundQuery: () => {
throw new Error(
'You must register a useBackgroundQuery hook via the `GraphQLHooksProvider`.'
)
},

useReadQuery: () => {
throw new Error(
'You must register a useReadQuery hook via the `GraphQLHooksProvider`.'
)
},
})

interface GraphQlHooksProviderProps<
TuseQuery = DefaultUseQueryType,
TuseMutation = DefaultUseMutationType,
TuseSubscription = DefaultUseSubscriptionType
> extends GraphQLHooks<TuseQuery, TuseMutation, TuseSubscription> {
TuseSubscription = DefaultUseSubscriptionType,
TuseSuspenseQuery = DefaultUseSuspenseType
> extends GraphQLHooks<
TuseQuery,
TuseMutation,
TuseSubscription,
TuseSuspenseQuery
> {
children: React.ReactNode
}

Expand All @@ -74,6 +127,9 @@ export const GraphQLHooksProvider = <
useQuery,
useMutation,
useSubscription,
useSuspenseQuery,
useBackgroundQuery,
useReadQuery,
children,
}: GraphQlHooksProviderProps<TuseQuery, TuseMutation>) => {
return (
Expand All @@ -82,6 +138,9 @@ export const GraphQLHooksProvider = <
useQuery,
useMutation,
useSubscription,
useSuspenseQuery,
useBackgroundQuery,
useReadQuery,
}}
>
{children}
Expand Down Expand Up @@ -127,3 +186,29 @@ export function useSubscription<
TVariables
>(query, options)
}

export function useSuspenseQuery<
TData = any,
TVariables extends OperationVariables = GraphQLOperationVariables
>(
query: DocumentNode,
options?: GraphQLSuspenseQueryHookOptions<TData, TVariables>
): SuspenseQueryOperationResult<TData, TVariables> {
return React.useContext(GraphQLHooksContext).useSuspenseQuery<
TData,
TVariables
>(query, options)
}

export const useBackgroundQuery: typeof apolloUseBackgroundQuery<any> = (
...args
) => {
// @TODO something about the apollo types here mean I need to override the return type
return React.useContext(GraphQLHooksContext).useBackgroundQuery(
...args
) as any
}

export const useReadQuery: typeof apolloUseReadQuery = (...args) => {
return React.useContext(GraphQLHooksContext).useReadQuery(...args)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import React from 'react'

import type { CellFailureProps } from './createCell'
import type { CellFailureProps } from './cellTypes'

type CellErrorBoundaryProps = {
export type FallbackProps = {
error: QueryOperationResult['error']
resetErrorBoundary: () => void
}

export type CellErrorBoundaryProps = {
// Note that the fallback has to be an FC, not a Node
// because the error comes from this component's state
fallback: React.FC<CellFailureProps>
renderFallback: (
fbProps: FallbackProps
) => React.ReactElement<CellFailureProps>
children: React.ReactNode
}

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

componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// @TODO do something with this?
console.log(error, errorInfo)
console.log('Cell failure: ', {
error,
errorInfo,
})
}

render() {
const { fallback: Fallback } = this.props
// The fallback is constructed with all the props required, except error and errorCode
// in createSusepndingCell.tsx
const { renderFallback } = this.props

if (this.state.hasError) {
return (
<Fallback
error={this.state.error}
errorCode={
this.state.error?.graphQLErrors?.[0]?.extensions?.['code'] as string
}
// @TODO (STREAMING) query-result not available here
/>
)
return renderFallback({
error: this.state.error,
resetErrorBoundary: () => {
this.setState({ hasError: false, error: undefined })
},
})
}

return this.props.children
Expand Down
Loading

0 comments on commit 069101b

Please sign in to comment.