Skip to content

Instantly share code, notes, and snippets.

@litewarp
Last active March 23, 2023 21:04
Show Gist options
  • Save litewarp/cc64c7624cad5702923aef62fcb2ff31 to your computer and use it in GitHub Desktop.
Save litewarp/cc64c7624cad5702923aef62fcb2ff31 to your computer and use it in GitHub Desktop.
Relay Nextjs13
'use client'
import {graphql} from 'react-relay'
import graphqlQuery, {
AllSecuritiesPageQuery
} from '~/__generated__/AllSecuritiesPageQuery.graphql'
import {createRelayHydrator} from '~/lib/relay/create-relay-hydrator'
export const {useHydratedPreloadedQuery, Hydrator} =
createRelayHydrator<AllSecuritiesPageQuery>(graphqlQuery)
const query = graphql`
query AllSecuritiesPageQuery(
$first: Int = 20
$after: Cursor
$filter: SecurityFilter
$orderBy: [SecuritiesOrderBy!]
) {
...SecuritiesTable_securitiesConnection
}
`
export function AllSecuritiesPage() {
const data = useHydratedPreloadedQuery(query)
return (
<></>
)
}
import {createContext, ReactNode, Suspense, useContext, useMemo} from 'react'
import {
GraphQLTaggedNode,
PreloadedQuery,
usePreloadedQuery,
useRelayEnvironment
} from 'react-relay'
import {RelayNetworkLayer} from 'react-relay-network-modern'
import {
ConcreteRequest,
GraphQLResponse,
OperationType,
RequestParameters
} from 'relay-runtime'
import {CacheOption, PreloadedQueryResponse} from '~/lib/relay/types'
export type RelayHydratorOptions = {
cache?: CacheOption
}
const endpoint = process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT as string
export const getFetchKey = (params: RequestParameters): string => {
return params.text ? params.name : params.id ?? ''
}
export function createRelayHydrator<TOperation extends OperationType>(
query: ConcreteRequest,
options?: RelayHydratorOptions
) {
if (!query) throw new Error(`Graphql Request required!`)
if (!endpoint)
throw new Error(`Must set NEXT_PUBLIC_GRAPHQL_ENDPOINT as an .env variable`)
const opts = options ?? ({} as RelayHydratorOptions)
const Context = createContext<{query: PreloadedQuery<TOperation>}>(
{} as {query: PreloadedQuery<TOperation>}
)
function PreloadedQueryProvider(props: {
children: ReactNode
query: PreloadedQueryResponse<TOperation>
}) {
const environment = useRelayEnvironment()
const query: PreloadedQuery<TOperation> = {
environment: environment,
fetchKey: getFetchKey(props.query.params),
fetchPolicy: 'store-or-network',
isDisposed: false,
name: props.query.params.name,
kind: 'PreloadedQuery',
variables: props.query.variables,
dispose: () => {
return // no-op
}
}
return <Context.Provider value={{query}}>{props.children}</Context.Provider>
}
function Hydrator(props: {
query: PreloadedQueryResponse<TOperation>
children: ReactNode
}) {
const {query, children} = props
const environment = useRelayEnvironment()
const network = environment.getNetwork() as RelayNetworkLayer
useMemo(() => {
const cache = network.responseCache
const key = getFetchKey(query.params)
cache.set(key, query.variables, query.response as GraphQLResponse)
}, [network.responseCache, query])
return (
<PreloadedQueryProvider query={query}>
<Suspense fallback={<>Loading ...</>}>{children}</Suspense>
</PreloadedQueryProvider>
)
}
function useHydratedPreloadedQuery(query: GraphQLTaggedNode) {
const context = useContext(Context)
return usePreloadedQuery(query, context.query)
}
return {
useHydratedPreloadedQuery,
Hydrator
}
}
import {ServerGetToken} from '../types'
import {OperationType, RequestParameters} from 'relay-runtime'
import {z} from 'zod'
import {CacheOption, PreloadedQueryResponse} from '~/lib/relay/types'
const endpoint = process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT as string
// validate the new nextjs cache options for fetch requests
// see https://beta.nextjs.org/docs/api-reference/fetch#optionscache
const cacheSchema = z.union([
z.object({
next: z.object({
revalidate: z.number()
})
}),
z.object({
cache: z.enum(['no-store', 'force-cache'])
})
])
export const getCacheOption = (option: unknown): CacheOption => {
const valid = cacheSchema.safeParse(option)
// default it to no-store for now
return valid.success ? valid.data : {cache: 'no-store'}
}
export async function getPreloadedQuery<TQuery extends OperationType>(args: {
params: RequestParameters
variables: TQuery['variables']
getToken: ServerGetToken
cache?: CacheOption
}): Promise<PreloadedQueryResponse<TQuery>> {
const {params, variables, getToken, cache} = args
const token = await getToken()
const res = await fetch(endpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${token ?? ''}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: params.id,
query: params.text,
variables
}),
...getCacheOption(cache)
})
if (!res.ok) {
throw new Error(
`Error preloading query. Server responded with ${res.status}: ${res.statusText}`
)
}
return {
params,
variables,
response: await res.json()
}
}
import {auth} from '../getToken'
import graphqlQuery, {
AllSecuritiesPageQuery
} from '~/__generated__/AllSecuritiesPageQuery.graphql'
import {AllSecuritiesPage, Hydrator} from '~/app/admin/securities/AllSecuritiesPage'
import {getPreloadedQuery} from '~/lib/relay/get-preloaded-query'
export default async function AllSecuritiesRootPage() {
const {getToken} = auth()
const query = await getPreloadedQuery<AllSecuritiesPageQuery>({
params: graphqlQuery.params,
variables: {
first: 20
},
getToken
})
return (
<Hydrator query={query}>
<AllSecuritiesPage />
</Hydrator>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment