Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(streaming): Make Cells render on the server with useBackgroundQuery and useReadQuery #9074

Merged
merged 27 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1d263fc
Move Cell things into a folder
dac09 Aug 27, 2023
9109597
Move empty helpers to file to share
dac09 Aug 27, 2023
fecacd3
createSuspenseCell
dac09 Aug 27, 2023
5468b00
Add alias to import apollo provider directly
dac09 Aug 27, 2023
474c234
Rename createSuspendingCell to createCell to make it easier on the ba…
dac09 Aug 27, 2023
e91475d
Get it working by passing in all Apollo hooks
dac09 Aug 27, 2023
94fa45c
Comments
dac09 Aug 27, 2023
9fb11c9
Cleanup & "Fix" types
dac09 Aug 28, 2023
90dd8df
Add some more queryResult stuff
dac09 Aug 28, 2023
ad4b257
Restore queryResult type. Problem wasn't on my end
dac09 Aug 28, 2023
042f488
Remove suspense proxy
dac09 Aug 28, 2023
9daaae9
Update error boundary
dac09 Aug 28, 2023
1efc01a
Prettify
dac09 Aug 28, 2023
ac17aea
Merge branch 'main' into feat/create-suspending-cell
dac09 Aug 28, 2023
9c7dc8e
Hooks provider types
dac09 Aug 28, 2023
62c8bcf
Merge branch 'main' into feat/create-suspending-cell
dac09 Aug 28, 2023
ae32bad
Fix resetting of error boundary
dac09 Aug 29, 2023
1674e45
Cleanup
dac09 Aug 29, 2023
f4ed147
Merge branch 'main' into feat/create-suspending-cell
dac09 Aug 29, 2023
b5774cf
Fix broken test
dac09 Aug 29, 2023
922a0f4
Add some basic tests for createSuspendingCell
dac09 Aug 29, 2023
00c5329
Update comments in cellTypes
dac09 Aug 29, 2023
fd34425
Merge branch 'main' into feat/create-suspending-cell
dac09 Aug 29, 2023
ba290f7
Silly lint
dac09 Aug 29, 2023
9553de1
Merge branch 'main' into feat/create-suspending-cell
dac09 Aug 29, 2023
5177391
Merge branch 'main' into feat/create-suspending-cell
dac09 Aug 30, 2023
756647e
Merge branch 'main' into feat/create-suspending-cell
dac09 Aug 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/web/apollo/suspense.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* eslint-env es6, commonjs */
module.exports = require('../dist/apollo/suspense')
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 {}
92 changes: 88 additions & 4 deletions packages/web/src/components/GraphQLHooksProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { OperationVariables } from '@apollo/client'
import type {
OperationVariables,
SuspenseQueryHookOptions,
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
*/

type DefaultUseQueryType = <
TData = any,
TVariables extends OperationVariables = GraphQLOperationVariables
Expand All @@ -24,14 +37,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 +78,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`. Make sure you are importing the correct Apollo provider in App.tsx'
)
},

useReadQuery: () => {
throw new Error(
'You must register a useReadQuery hook via the `GraphQLHooksProvider`. Make sure you are importing the correct Apollo provider in App.tsx'
)
},
})

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 +126,9 @@ export const GraphQLHooksProvider = <
useQuery,
useMutation,
useSubscription,
useSuspenseQuery,
useBackgroundQuery,
useReadQuery,
children,
}: GraphQlHooksProviderProps<TuseQuery, TuseMutation>) => {
return (
Expand All @@ -82,6 +137,9 @@ export const GraphQLHooksProvider = <
useQuery,
useMutation,
useSubscription,
useSuspenseQuery,
useBackgroundQuery,
useReadQuery,
}}
>
{children}
Expand Down Expand Up @@ -127,3 +185,29 @@ export function useSubscription<
TVariables
>(query, options)
}

export function useSuspenseQuery<
TData = any,
TVariables extends OperationVariables = GraphQLOperationVariables
>(
query: DocumentNode,
options?: SuspenseQueryHookOptions<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,11 @@
import React from 'react'

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

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>
fallback?: React.FC<CellFailureProps>
children: React.ReactNode
}

Expand Down Expand Up @@ -33,15 +33,18 @@ export class CellErrorBoundary extends React.Component<
}

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

// @TODO what happens when no Fallback supplied??
if (this.state.hasError && Fallback) {
return (
<Fallback
error={this.state.error}
errorCode={
this.state.error?.graphQLErrors?.[0]?.extensions?.['code'] as string
}
// @TODO (STREAMING) query-result not available here
/>
)
}
Expand Down
Loading