From 40ec191a0db0dba14d08f7d40ca93735a6db8a8d Mon Sep 17 00:00:00 2001 From: Christopher Pappas Date: Mon, 21 May 2018 23:30:22 -0700 Subject: [PATCH] feat: use `fetch` response data if schema is not passed (thanks @damassi) * Use fetch data if schema is not passed * Update README * Add `lookup` option --- README.md | 67 +++++++++++++++++++++++++++++++++++++++++---------- src/client.js | 15 +++++++++--- src/server.js | 21 ++++++++++------ 3 files changed, 79 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index a0be81e..3032399 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ SSR middleware for `react-relay-network-modern` (for Relay Modern) ![FlowType compatible](https://img.shields.io/badge/flowtype-compatible-brightgreen.svg) +**For a full examples, see**: + - https://github.com/damassi/react-relay-network-modern-ssr-example + - https://github.com/damassi/react-relay-network-modern-ssr-todomvc + Server ======= ```js @@ -18,25 +22,47 @@ import schema from './schema'; const relayServerSSR = new RelayServerSSR(); const network = new RelayNetworkLayer([ + // There are three possible ways relayServerSSR.getMiddleware() can be used; + // choose the one that best matches your context: + + // By default, if called without arguments it will use `fetch` under the hood + // to request data. (See https://github.com/nodkz/react-relay-network-modern + // for more info) + relayServerSSR.getMiddleware(), + + // Or, you can directly pass in a GraphQL schema, which will use `graphql` + // from `graphql-js` to request data relayServerSSR.getMiddleware({ schema, contextValue: {}, }), - // OR if you need to prepare context in async mode - // relayServerSSR.getMiddleware(async () => ({ - // schema, - // contextValue: await prepareGraphQLContext(), - // })), + + // If you need to prepare context in async mode, `getMiddleware` will also + // accept a function: + relayServerSSR.getMiddleware(async () => ({ + schema, + contextValue: await prepareGraphQLContext(), + })), ]); -// ... first APP render for starting relay queries -// ReactDOMServer.renderToString(); +// Once the RelayNetworkLayer is instantiated, two App renders need to be made in +// order to prepare data for hydration: + +// First, kick off Relay requests with an initial render +ReactDOMServer.renderToString(); -const relayData: string = await relayServerSSR.getCache(); +// Second, collect the data +const relayData = await relayServerSSR.getCache(); + +// Third, render the app a second time now that the Relay store has been primed +// and send HTML and bootstrap data to the client for rehydration. (setTimeout is +// used to ensure that async resolution inside of QueryRenderer completes once +// data is provided. +setTimeout(() => { + const appHtml = ReactDOMServer.renderToString(); + sendHtml(appHtml, relayData); +}, 0) -// ... second APP render with already loaded payload -// const appHtml = ReactDOMServer.renderToString(); -// sendHtml(appHtml, relayData); ``` Client @@ -48,10 +74,25 @@ import RelayClientSSR from 'react-relay-network-modern-ssr/lib/client'; const relayClientSSR = new RelayClientSSR(window.relayData); const network = new RelayNetworkLayer([ - relayClientSSR.getMiddleware(), + relayClientSSR.getMiddleware({ + // Will preserve cache rather than purge after mount. This works great with + // cacheMiddleware. + lookup: false + }), ]); -``` +... + +ReactDOM.render( + , + mountPoint +) + +``` Contribute ========== I actively welcome pull requests with code and doc fixes. diff --git a/src/client.js b/src/client.js index 0ff036b..1b8019a 100644 --- a/src/client.js +++ b/src/client.js @@ -4,6 +4,10 @@ import type { MiddlewareSync, QueryPayload } from 'react-relay-network-modern/li import type { SSRCache } from './server'; import { getCacheKey } from './utils'; +type SSRGraphQLArgs = {| + lookup?: boolean, +|}; + export default class RelayClientSSR { cache: ?Map; debug: boolean; @@ -16,7 +20,7 @@ export default class RelayClientSSR { } } - getMiddleware(): MiddlewareSync { + getMiddleware(args: SSRGraphQLArgs): MiddlewareSync { return { execute: (operation, variables) => { const cache = this.cache; @@ -26,9 +30,12 @@ export default class RelayClientSSR { const payload = cache.get(cacheKey); this.log('SSR_CACHE_GET', cacheKey, payload); - cache.delete(cacheKey); - if (cache.size === 0) { - this.cache = null; + const lookup = args && args.lookup; + if (!lookup) { + cache.delete(cacheKey); + if (cache.size === 0) { + this.cache = null; + } } return payload; diff --git a/src/server.js b/src/server.js index 3ca70c3..4606df5 100644 --- a/src/server.js +++ b/src/server.js @@ -31,7 +31,7 @@ export default class RelayServerSSR { } getMiddleware(args: SSRGraphQLArgs | (() => Promise)): Middleware { - return () => async (r: any) => { + return next => async (r: any) => { const req: RelayRequest = r; const cacheKey = getCacheKey(req.operation.name, req.variables); @@ -42,17 +42,24 @@ export default class RelayServerSSR { } this.log('Run graphql query', cacheKey); + + const graphqlArgs: SSRGraphQLArgs = isFunction(args) ? await args() : (args: any); + const hasSchema = graphqlArgs && graphqlArgs.schema; const gqlResponse = new Promise(async (resolve, reject) => { setTimeout(() => { reject(new Error('RelayRequest timeout')); }, 30000); - const graphqlArgs: SSRGraphQLArgs = isFunction(args) ? await args() : (args: any); - const payload = await graphql({ - ...graphqlArgs, - source: req.getQueryString(), - variableValues: req.getVariables(), - }); + let payload = null; + if (hasSchema) { + payload = await graphql({ + ...graphqlArgs, + source: req.getQueryString(), + variableValues: req.getVariables(), + }); + } else { + payload = await next(r); + } resolve(payload); }); this.cache.set(cacheKey, gqlResponse);