Skip to content

Commit

Permalink
feat: use fetch response data if schema is not passed (thanks @damassi
Browse files Browse the repository at this point in the history
)

* Use fetch data if schema is not passed
* Update README
* Add `lookup` option
  • Loading branch information
damassi authored and nodkz committed May 22, 2018
1 parent 3de7e67 commit 40ec191
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 24 deletions.
67 changes: 54 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(<App />);
// 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(<App />);

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(<App />);
sendHtml(appHtml, relayData);
}, 0)

// ... second APP render with already loaded payload
// const appHtml = ReactDOMServer.renderToString(<App />);
// sendHtml(appHtml, relayData);
```

Client
Expand All @@ -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(
<QueryRenderer
dataFrom="STORE_THEN_NETWORK" // Required for Relay 1.5
environment={environment}
...
/>,
mountPoint
)

```
Contribute
==========
I actively welcome pull requests with code and doc fixes.
Expand Down
15 changes: 11 additions & 4 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, QueryPayload>;
debug: boolean;
Expand All @@ -16,7 +20,7 @@ export default class RelayClientSSR {
}
}

getMiddleware(): MiddlewareSync {
getMiddleware(args: SSRGraphQLArgs): MiddlewareSync {
return {
execute: (operation, variables) => {
const cache = this.cache;
Expand All @@ -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;
Expand Down
21 changes: 14 additions & 7 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default class RelayServerSSR {
}

getMiddleware(args: SSRGraphQLArgs | (() => Promise<SSRGraphQLArgs>)): Middleware {
return () => async (r: any) => {
return next => async (r: any) => {
const req: RelayRequest = r;
const cacheKey = getCacheKey(req.operation.name, req.variables);

Expand All @@ -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);
Expand Down

0 comments on commit 40ec191

Please sign in to comment.