-
-
Save maraisr/515ccaa3d04ab7ce6eea969578f5db49 to your computer and use it in GitHub Desktop.
import fetch from 'isomorphic-unfetch'; | |
import React, { createContext, useContext, useMemo } from 'react'; | |
import { ReactRelayContext } from 'react-relay'; | |
import { | |
Environment, | |
FetchFunction, | |
Network, | |
RecordSource, | |
RequestParameters, | |
Store, | |
Variables, | |
} from 'relay-runtime'; | |
import { config } from '../config'; | |
const fetchQuery: FetchFunction = async ( | |
request: RequestParameters, | |
variables: Variables, | |
) => { | |
const response: Response = await fetch( | |
`${config.api.gatewayUrl}/graph/graphql`, | |
{ | |
method: 'POST', | |
cache: 'no-cache', | |
headers: { | |
Accept: 'application/json', | |
'Content-Type': 'application/json', | |
'x-api-key': config.api.key, | |
}, | |
body: JSON.stringify({ | |
query: request.text, | |
variables, | |
doc_id: request.id, | |
}), | |
}, | |
); | |
const data = await response.json(); | |
if (response.status >= 400) { | |
throw data.errors; | |
} | |
if (isMutation(request) && data.errors) { | |
throw data; | |
} | |
if (!data.data) { | |
throw data.errors; | |
} | |
return data; | |
}; | |
const network = Network.create(fetchQuery); | |
const createEnvironment = ( | |
records: ConstructorParameters<typeof RecordSource>[0] = {}, | |
): Environment => { | |
const source = new RecordSource(records); | |
const store = new Store(source); | |
return new Environment({ | |
network, | |
store, | |
}); | |
}; | |
let memoEnv: Environment = null; | |
export const getEnvironment = ( | |
records: ConstructorParameters<typeof RecordSource>[0] = {}, | |
) => { | |
if (process.browser && memoEnv === null) { | |
memoEnv = createEnvironment(records); | |
return memoEnv; | |
} | |
if (process.browser) { | |
return memoEnv; | |
} | |
return createEnvironment(records); | |
}; | |
const isMutation = (request: RequestParameters) => | |
request.operationKind === 'mutation'; | |
const EnvironmentContext = createContext<Environment>(null); | |
export const EnvironmentProvider = ({ | |
children, | |
initRecords, | |
initVariables, | |
}) => { | |
const environment = useMemo(() => getEnvironment(initRecords), [ | |
initRecords, | |
]); | |
const relayContextInit = useMemo( | |
() => ({ environment, variables: initVariables }), | |
[environment, initVariables], | |
); | |
return ( | |
<EnvironmentContext.Provider value={environment}> | |
<ReactRelayContext.Provider value={relayContextInit}> | |
{children} | |
</ReactRelayContext.Provider> | |
</EnvironmentContext.Provider> | |
); | |
}; | |
export const useEnvironment = (): Environment => useContext(EnvironmentContext); |
import { NextPage, NextPageContext } from 'next'; | |
import Error from 'next/error'; | |
import React, { ComponentType } from 'react'; | |
import { fetchQuery, graphql } from 'react-relay'; | |
import { OperationType } from 'relay-runtime'; | |
import { EnvironmentProvider, getEnvironment } from './environment'; | |
interface DefaultProps { | |
hasError?: boolean; | |
statusCode?: number; | |
} | |
interface Options<Query extends OperationType, Props> { | |
query: ReturnType<typeof graphql>; | |
variables?: (ctx: NextPageContext) => Promise<Query['variables']>; | |
getInitialProps?: ( | |
ctx: NextPageContext, | |
query: Query, | |
) => Promise<Omit<Props, 'records' | 'query'> & DefaultProps>; | |
} | |
export function withData<Query extends OperationType, Props extends {} = {}>( | |
ComposedComponent: ComponentType<Query>, | |
options: Options<Query, Props>, | |
) { | |
const WithDataComponent: NextPage<{ | |
query: Query; | |
records: any; | |
} & DefaultProps> = props => { | |
return props.hasError ? ( | |
<Error statusCode={props.statusCode} /> | |
) : ( | |
<EnvironmentProvider | |
initRecords={props.records} | |
initVariables={props.query.variables}> | |
<ComposedComponent {...props.query} /> | |
</EnvironmentProvider> | |
); | |
}; | |
WithDataComponent.getInitialProps = async ctx => { | |
const environment = getEnvironment(); | |
const variables: Query['variables'] = options.variables | |
? await options.variables(ctx) | |
: {}; | |
const queryProps: Query['response'] = await fetchQuery( | |
environment, | |
options.query, | |
variables, | |
); | |
const queryRecords = environment | |
.getStore() | |
.getSource() | |
.toJSON(); | |
// @ts-ignore | |
const query: Query = { | |
response: queryProps, | |
variables, | |
} as const; | |
const { statusCode = 200, hasError = false, ...otherProps } = | |
typeof options.getInitialProps === 'function' | |
? await options.getInitialProps(ctx, query) | |
: {}; | |
if (!process.browser && hasError && ctx) { | |
// eslint-disable-next-line require-atomic-updates | |
ctx.res.statusCode = statusCode; | |
ctx.res.setHeader( | |
'Cache-Control', | |
'no-cache, no-store, must-revalidate', | |
); | |
ctx.res.setHeader('Pragma', 'no-cache'); | |
ctx.res.setHeader('Expires', -1); | |
} | |
return { | |
query, | |
records: queryRecords, | |
hasError, | |
statusCode, | |
...otherProps, | |
} as const; | |
}; | |
return WithDataComponent; | |
} |
Hi @tony, yeah by all means use it. Im working on a new better one which I can share with you also, this particular one has some flaws. So if you wanted to check back in about a week, I can share it with you!
It's largely influence by this: https://dev.to/marais/relay-and-ssr-using-createoperationdescriptor-k6f
@maraisr yeah definitely and thank you for the link. I'm on day #2 with next.js
Interested in how the implementation looks and what the deployment looks like. I'm using next.js with a graphene (django) website atm and getting it on SSR
Hello @maraisr, thank you for putting this together! Do you have any other resource that has a complete example of this working. Even though I'm well-acquainted with Next & React & Apollo, putting Typescript, Next, and Relay together seemed to be the perfect storm.
Looking for a comprehensive example if you've finished the one you were talking about! Really appreciate it!
@maraisr Any example of how you would use
withData
? Is this from a GH issue?Is it / can you license it for reuse on MIT license/whatever license relay uses?