Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New example: with-relay-modern-typescript #10790

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/with-relay-modern-typescript/.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-typescript/.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
2 changes: 2 additions & 0 deletions examples/with-relay-modern-typescript/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__generated__/
schema/schema.graphql
8 changes: 8 additions & 0 deletions examples/with-relay-modern-typescript/.graphqlconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"schemaPath": "schema/schema.graphql",
"extensions": {
"endpoints": {
"dev": "https://api.graph.cool/relay/v1/next-js-with-relay-modern-example"
}
}
}
66 changes: 66 additions & 0 deletions examples/with-relay-modern-typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Relay Modern Typescript Example

One of the strengths of GraphQL is [enforcing data types on runtime](https://graphql.github.io/graphql-spec/June2018/#sec-Value-Completion). Further, TypeScript and [Relay Compiler](https://www.npmjs.com/package/relay-compiler) make it safer by typing data statically, so you can write truly type-protected code with rich IDE assists.

This template extends [Relay Modern Example](https://github.com/zeit/next.js/tree/canary/examples/with-relay-modern) by rewriting in TypeScript and integrating type generation care of [Relay Compiler](https://www.npmjs.com/package/relay-compiler) under the hood.

## Deploy your own

Deploy the example using [ZEIT Now](https://zeit.co/now):

[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-relay-modern)

## How to use

### Using `create-next-app`

Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:

```bash
npm init next-app --example with-relay-modern-typescript with-relay-modern-typescript-app
# or
yarn create next-app --example with-relay-modern-typescript with-relay-modern-typescript-app
```

### Download manually

Download the example:

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

Install it:

```bash
npm install
# or
yarn
```

Download schema introspection data from configured Relay endpoint

```bash
npm run schema
# or
yarn schema
```

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

```bash
npm run relay
# or
yarn relay
```

Run the project

```bash
npm run dev
# or
yarn dev
```

Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'
import { createFragmentContainer, graphql } from 'react-relay'
import { BlogPostPreview_post } from './__generated__/BlogPostPreview_post.graphql'

const BlogPostPreview = ({ post }: { post: BlogPostPreview_post }) => (
<li>{post.title}</li>
)

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

const BlogPosts = ({ viewer }: { viewer: BlogPosts_viewer }) => (
<div>
<h1>Blog posts</h1>
<ul>
{viewer.allBlogPosts.edges?.map(
e => e?.node && <BlogPostPreview key={e.node.id} post={e.node} />
)}
</ul>
</div>
)

export default createFragmentContainer(BlogPosts, {
viewer: graphql`
fragment BlogPosts_viewer on Viewer {
allBlogPosts(first: 10, orderBy: createdAt_DESC) {
edges {
node {
...BlogPostPreview_post
id
}
}
}
}
`,
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
Environment,
Network,
RecordSource,
Store,
FetchFunction,
} from 'relay-runtime'
import fetch from 'isomorphic-unfetch'

let relayEnvironment: Environment | null = null

// Define a function that fetches the results of an operation (query/mutation/etc)
// and returns its results as a Promise:
const fetchQuery: FetchFunction = (operation, variables) => {
return fetch(process.env.RELAY_ENDPOINT as string, {
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 = {} } = {}): Environment {
// 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 (typeof window === 'undefined') {
return new Environment({
network,
store,
})
}

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

return relayEnvironment
}
65 changes: 65 additions & 0 deletions examples/with-relay-modern-typescript/lib/withData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react'
import { NextPage, NextPageContext } from 'next'
import { fetchQuery, ReactRelayContext } from 'react-relay'
import { Environment, GraphQLTaggedNode } from 'relay-runtime'
import initEnvironment from './createRelayEnvironment'

export default (
ComposedComponent: NextPage,
options: { query?: GraphQLTaggedNode } = {}
) => {
return class WithData extends React.Component {
static displayName = `WithData(${ComposedComponent.displayName})`
environment: Environment

static async getInitialProps(ctx: NextPageContext) {
// 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
)) as any
queryRecords = environment
.getStore()
.getSource()
.toJSON()
}

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

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

render() {
return (
<ReactRelayContext.Provider value={{ environment: this.environment }}>
<ComposedComponent {...this.props} />
</ReactRelayContext.Provider>
)
}
}
}
2 changes: 2 additions & 0 deletions examples/with-relay-modern-typescript/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
22 changes: 22 additions & 0 deletions examples/with-relay-modern-typescript/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
},
}
38 changes: 38 additions & 0 deletions examples/with-relay-modern-typescript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "with-relay-modern-typescript",
"version": "3.0.4",
"description": "Example of Next.js with Relay Modern SSR and Typescript",
"scripts": {
"graphcool-init": "graphcool init --schema schema/init-schema.graphql",
"dev": "next",
"build": "next build",
"start": "next start",
"relay": "relay-compiler --language typescript --src ./ --exclude '**/.next/**' '**/node_modules/**' '**/test/**' '**/__generated__/**' --exclude '**/schema/**' --schema ./schema/schema.graphql",
"schema": "graphql get-schema -e dev"
},
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^8.2.0",
"dotenv-webpack": "^1.7.0",
"graphql": "^14.5.8",
"isomorphic-unfetch": "^3.0.0",
"next": "latest",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-relay": "^8.0.0"
},
"devDependencies": {
"@types/node": "13.7.7",
"@types/react": "16.9.23",
"@types/react-dom": "16.9.5",
"@types/react-relay": "7.0.3",
"@types/relay-runtime": "8.0.6",
"babel-plugin-relay": "^8.0.0",
"graphcool": "^1.4.0",
"graphql-cli": "^3.0.14",
"relay-compiler": "^8.0.0",
"relay-compiler-language-typescript": "^12.0.0",
"typescript": "3.8.3"
}
}
16 changes: 16 additions & 0 deletions examples/with-relay-modern-typescript/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'
import { NextPage, NextPageContext } from 'next'
import withData from '../lib/withData'
import BlogPosts from '../components/BlogPosts'
import indexPageQuery from '../queries/indexPage'
import { indexPage_indexQueryResponse } from '../queries/__generated__/indexPage_indexQuery.graphql'

const Index = ({ viewer }: NextPageContext & indexPage_indexQueryResponse) => (
<div>
<BlogPosts viewer={viewer} />
</div>
)

export default withData(Index as NextPage, {
query: indexPageQuery,
})
9 changes: 9 additions & 0 deletions examples/with-relay-modern-typescript/queries/indexPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { graphql } from 'react-relay'

export default graphql`
query indexPage_indexQuery {
viewer {
...BlogPosts_viewer
}
}
`
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!
}
24 changes: 24 additions & 0 deletions examples/with-relay-modern-typescript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"allowJs": true,
"alwaysStrict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "preserve",
"lib": ["dom", "es2017"],
"module": "esnext",
"moduleResolution": "node",
"noEmit": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext",
"allowSyntheticDefaultImports": true
},
"exclude": ["node_modules"],
"include": ["**/*.ts", "**/*.tsx"]
}