Skip to content

Commit

Permalink
Relay Modern Example (#1757) (#2773)
Browse files Browse the repository at this point in the history
  • Loading branch information
petrvlcek authored and timneutkens committed Aug 14, 2017
1 parent 6285cde commit 708193d
Show file tree
Hide file tree
Showing 13 changed files with 352 additions and 0 deletions.
8 changes: 8 additions & 0 deletions examples/with-relay-modern/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"presets": [
"next/babel"
],
"plugins": [
"relay"
]
}
1 change: 1 addition & 0 deletions examples/with-relay-modern/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
RELAY_ENDPOINT=https://api.graph.cool/relay/v1/next-js-with-relay-modern-example
52 changes: 52 additions & 0 deletions examples/with-relay-modern/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-relay-modern)
# Relay Modern Example

## How to use

Download the example [or clone the repo](https://github.com/zeit/next.js):

```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-relay-modern
cd with-relay-modern
```

Install it:

```bash
npm install
```

Setup Graphcool project

```bash
npm run graphcool-init
```

After successful initialization, copy Graphcool Relay Endpoint URL from console and store it into `.env` file
```bash
RELAY_ENDPOINT=your relay endpoint here
```

Run Relay ahead-of-time compilation (should be re-run after any edits to components that query data with Relay)
```bash
npm run relay
```

Run the project
```bash
npm run dev
```

Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)):

```bash
now
```

## The idea behind the example

[Relay Modern](https://facebook.github.io/relay/docs/relay-modern.html) is a new version of Relay designed from the ground up to be easier to use, more extensible and, most of all, able to improve performance on mobile devices. Relay Modern accomplishes this with static queries and ahead-of-time code generation.

In this simple example, we integrate Relay Modern seamlessly with Next by wrapping our *pages* inside a [higher-order component (HOC)](https://facebook.github.io/react/docs/higher-order-components.html). Using the HOC pattern we're able to pass down a query result data created by Relay into our React component hierarchy defined inside each page of our Next application. The HOC takes `options` argument that allows to specify a `query` that will be executed on the server when a page is being loaded.

This example relies on [graph.cool](https://www.graph.cool) for its GraphQL backend.
17 changes: 17 additions & 0 deletions examples/with-relay-modern/components/BlogPostPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'
import { createFragmentContainer, graphql } from 'react-relay'

const BlogPostPreview = props => {
return (
<div key={props.post.id}>{props.post.title}</div>
)
}

export default createFragmentContainer(BlogPostPreview, {
post: graphql`
fragment BlogPostPreview_post on BlogPost {
id
title
}
`
})
28 changes: 28 additions & 0 deletions examples/with-relay-modern/components/BlogPosts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react'
import { createFragmentContainer, graphql } from 'react-relay'
import BlogPostPreview from './BlogPostPreview'

const BlogPosts = props => {
return (
<div>
<h1>Blog posts</h1>
{props.viewer.allBlogPosts.edges.map(({ node }) =>
<BlogPostPreview post={node} />
)}
</div>
)
}

export default createFragmentContainer(BlogPosts, {
viewer: graphql`
fragment BlogPosts_viewer on Viewer {
allBlogPosts(first: 10, orderBy: createdAt_DESC) {
edges {
node {
...BlogPostPreview_post
}
}
}
}
`
})
31 changes: 31 additions & 0 deletions examples/with-relay-modern/lib/RelayProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react'
import PropTypes from 'prop-types'

// Thank you https://github.com/robrichard
// https://github.com/robrichard/relay-context-provider

class RelayProvider extends React.Component {
getChildContext () {
return {
relay: {
environment: this.props.environment,
variables: this.props.variables
}
}
}
render () {
return this.props.children
}
}

RelayProvider.childContextTypes = {
relay: PropTypes.object.isRequired
}

RelayProvider.propTypes = {
environment: PropTypes.object.isRequired,
variables: PropTypes.object.isRequired,
children: PropTypes.node
}

export default RelayProvider
50 changes: 50 additions & 0 deletions examples/with-relay-modern/lib/createRelayEnvironment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Environment, Network, RecordSource, Store } from 'relay-runtime'
import fetch from 'isomorphic-unfetch'

let relayEnvironment = null

// Define a function that fetches the results of an operation (query/mutation/etc)
// and returns its results as a Promise:
function fetchQuery (
operation,
variables,
cacheConfig,
uploadables,
) {
return fetch(process.env.RELAY_ENDPOINT, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}, // Add authentication and other headers here
body: JSON.stringify({
query: operation.text, // GraphQL text from input
variables
})
}).then(response => response.json())
}

export default function initEnvironment ({ records = {} } = {}) {
// Create a network layer from the fetch function
const network = Network.create(fetchQuery)
const store = new Store(new RecordSource(records))

// Make sure to create a new Relay environment for every server-side request so that data
// isn't shared between connections (which would be bad)
if (!process.browser) {
return new Environment({
network,
store
})
}

// reuse Relay environment on client-side
if (!relayEnvironment) {
relayEnvironment = new Environment({
network,
store
})
}

return relayEnvironment
}
53 changes: 53 additions & 0 deletions examples/with-relay-modern/lib/withData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react'
import initEnvironment from './createRelayEnvironment'
import { fetchQuery } from 'react-relay'
import RelayProvider from './RelayProvider'

export default (ComposedComponent, options = {}) => {
return class WithData extends React.Component {
static displayName = `WithData(${ComposedComponent.displayName})`

static async getInitialProps (ctx) {
// Evaluate the composed component's getInitialProps()
let composedInitialProps = {}
if (ComposedComponent.getInitialProps) {
composedInitialProps = await ComposedComponent.getInitialProps(ctx)
}

let queryProps = {}
let queryRecords = {}
const environment = initEnvironment()

if (options.query) {
// Provide the `url` prop data in case a graphql query uses it
// const url = { query: ctx.query, pathname: ctx.pathname }
const variables = {}
// TODO: Consider RelayQueryResponseCache
// https://github.com/facebook/relay/issues/1687#issuecomment-302931855
queryProps = await fetchQuery(environment, options.query, variables)
queryRecords = environment.getStore().getSource().toJSON()
}

return {
...composedInitialProps,
...queryProps,
queryRecords
}
}

constructor (props) {
super(props)
this.environment = initEnvironment({
records: props.queryRecords
})
}

render () {
return (
<RelayProvider environment={this.environment} variables={{}}>
<ComposedComponent {...this.props} />
</RelayProvider>
)
}
}
}
22 changes: 22 additions & 0 deletions examples/with-relay-modern/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require('dotenv').config()

const path = require('path')
const Dotenv = require('dotenv-webpack')

module.exports = {
webpack: (config) => {
config.plugins = config.plugins || []

config.plugins = [
...config.plugins,

// Read the .env file
new Dotenv({
path: path.join(__dirname, '.env'),
systemvars: true
})
]

return config
}
}
29 changes: 29 additions & 0 deletions examples/with-relay-modern/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "with-relay-modern",
"version": "3.0.3",
"description": "Example of Next.js with Relay Modern SSR",
"scripts": {
"graphcool-init": "graphcool init --schema schema/init-schema.graphql",
"dev": "next",
"build": "next build",
"start": "next start",
"relay": "relay-compiler --src ./ --exclude **/.next/** **/node_modules/** **/test/** **/__generated__/** --schema ./schema/schema.graphql",
"schema": "node scripts/getSchema.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^4.0.0",
"dotenv-webpack": "^1.5.4",
"isomorphic-unfetch": "^2.0.0",
"next": "^3.0.3",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-relay": "^1.2.0-rc.1"
},
"devDependencies": {
"babel-plugin-relay": "^1.1.0",
"graphcool": "^1.2.1",
"relay-compiler": "^1.2.0-rc.1"
}
}
26 changes: 26 additions & 0 deletions examples/with-relay-modern/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { Component } from 'react'
import { graphql } from 'react-relay'
import withData from '../lib/withData'
import BlogPosts from '../components/BlogPosts'

class Index extends Component {
static displayName = `Index`

render (props) {

This comment has been minimized.

Copy link
@messa

messa Jan 1, 2019

Contributor

@petrvlcek hi :) I don't understand this props argument in render (props) - is it a real thing, or just some forgotten code?

return (
<div>
<BlogPosts viewer={this.props.viewer} />
</div>
)
}
}

export default withData(Index, {
query: graphql`
query pages_indexQuery {
viewer {
...BlogPosts_viewer
}
}
`
})
7 changes: 7 additions & 0 deletions examples/with-relay-modern/schema/init-schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type BlogPost implements Node {
content: String!
createdAt: DateTime!
id: ID! @isUnique
title: String!
updatedAt: DateTime!
}
28 changes: 28 additions & 0 deletions examples/with-relay-modern/scripts/getSchema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
var fetch = require('node-fetch')
var fs = require('fs')
var path = require('path')

require('dotenv').config()

const {
buildClientSchema,
introspectionQuery,
printSchema
} = require('graphql/utilities')

console.log(introspectionQuery)

fetch(process.env.RELAY_ENDPOINT, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ 'query': introspectionQuery })
})
.then(res => res.json())
.then(res => {
console.log(res)
const schemaString = printSchema(buildClientSchema(res.data))
fs.writeFileSync(path.join(__dirname, '..', 'schema', 'schema.graphql'), schemaString)
})

0 comments on commit 708193d

Please sign in to comment.