提交 915673fc 编写于 作者: L Luis Fernando Alvarez D 提交者: Tim Neutkens

Example updated: with-apollo-auth to Next 6 (#4420)

上级 db545d9b
{
"env": {
"development": {
"presets": "next/babel"
},
"production": {
"presets": "next/babel"
},
"test": {
"presets": [
["env", { "modules": "commonjs" }],
"next/babel"
]
}
}
}
.next
# Created by https://www.gitignore.io/api/vim,node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
### Vim ###
# swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-v][a-z]
[._]sw[a-p]
# session
Session.vim
# temporary
.netrwhist
*~
# auto-generated tag files
tags
# End of https://www.gitignore.io/api/vim,node
......@@ -54,7 +54,7 @@ This is an extention of the _[with Apollo](https://github.com/zeit/next.js/tree/
>
> This example relies on [graph.cool](https://www.graph.cool) for its GraphQL backend.
>
> *Note: Apollo uses Redux internally; if you're interested in integrating the client with your existing Redux store check out the [`with-apollo-and-redux`](https://github.com/zeit/next.js/tree/master/examples/with-apollo-and-redux) example.*
> *Note: If you're interested in integrating the client with your existing Redux store check out the [`with-apollo-and-redux`](https://github.com/zeit/next.js/tree/master/examples/with-apollo-and-redux) example.*
[graph.cool](https://www.graph.cool) can be setup with many different
[authentication providers](https://www.graph.cool/docs/reference/integrations/overview-seimeish6e/#authentication-providers), the most basic of which is [email-password authentication](https://www.graph.cool/docs/reference/simple-api/user-authentication-eixu9osueb/#email-and-password). Once email-password authentication is enabled for your graph.cool project, you are provided with 2 useful mutations: `createUser` and `signinUser`.
......
import gql from 'graphql-tag'
export default (context, apolloClient) => (
export default apolloClient => (
apolloClient.query({
query: gql`
query getUser {
......
import React from 'react'
import cookie from 'cookie'
import PropTypes from 'prop-types'
import { getDataFromTree } from 'react-apollo'
import Head from 'next/head'
import initApollo from './initApollo'
function parseCookies(req, options = {}) {
return cookie.parse(
req && req.headers.cookie
? req.headers.cookie
: document.cookie,
options
)
}
export default App => {
return class WithData extends React.Component {
static displayName = `WithData(${App.displayName})`
static propTypes = {
apolloState: PropTypes.shape({
data: PropTypes.object.isRequired
}).isRequired
}
static async getInitialProps(ctx) {
const { Component, router, ctx: { req, res } } = ctx
const apolloState = {}
const apollo = initApollo({}, {
getToken: () => parseCookies(req).token
})
ctx.ctx.apolloClient = apollo
let appProps = {}
if (App.getInitialProps) {
appProps = await App.getInitialProps(ctx)
}
if (res && res.finished) {
// When redirecting, the response is finished.
// No point in continuing to render
return {}
}
// Run all graphql queries in the component tree
// and extract the resulting data
try {
// Run all GraphQL queries
await getDataFromTree(
<App
{...appProps}
Component={Component}
router={router}
apolloState={apolloState}
apolloClient={apollo}
/>
)
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error)
}
if (!process.browser) {
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
}
// Extract query data from the Apollo's store
apolloState.data = apollo.cache.extract()
return {
...appProps,
apolloState
}
}
constructor(props) {
super(props)
// `getDataFromTree` renders the component first, the client is passed off as a property.
// After that rendering is done using Next's normal rendering pipeline
this.apolloClient =
props.apolloClient ||
initApollo(props.apolloState.data, {
getToken: () => parseCookies().token
})
}
render() {
return <App {...this.props} apolloClient={this.apolloClient} />
}
}
}
import React from 'react'
import cookie from 'cookie'
import PropTypes from 'prop-types'
import { ApolloProvider, getDataFromTree } from 'react-apollo'
import Head from 'next/head'
import initApollo from './initApollo'
function parseCookies(context = {}, options = {}) {
return cookie.parse(
context.req && context.req.headers.cookie
? context.req.headers.cookie
: document.cookie,
options
)
}
export default ComposedComponent => {
return class WithData extends React.Component {
static displayName = `WithData(${ComposedComponent.displayName})`
static propTypes = {
serverState: PropTypes.object.isRequired
}
static async getInitialProps(context) {
let serverState = {}
// Setup a server-side one-time-use apollo client for initial props and
// rendering (on server)
let apollo = initApollo({}, {
getToken: () => parseCookies(context).token
})
// Evaluate the composed component's getInitialProps()
let composedInitialProps = {}
if (ComposedComponent.getInitialProps) {
composedInitialProps = await ComposedComponent.getInitialProps(context, apollo)
}
// Run all graphql queries in the component tree
// and extract the resulting data
if (!process.browser) {
if (context.res && context.res.finished) {
// When redirecting, the response is finished.
// No point in continuing to render
return
}
// Provide the `url` prop data in case a graphql query uses it
const url = { query: context.query, pathname: context.pathname }
try {
// Run all GraphQL queries
const app = (
<ApolloProvider client={apollo}>
<ComposedComponent url={url} {...composedInitialProps} />
</ApolloProvider>
)
await getDataFromTree(app, {
router: {
query: context.query,
pathname: context.pathname,
asPath: context.asPath
}
})
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
// Extract query data from the Apollo's store
serverState = apollo.cache.extract()
}
return {
serverState,
...composedInitialProps
}
}
constructor(props) {
super(props)
// Note: Apollo should never be used on the server side beyond the initial
// render within `getInitialProps()` above (since the entire prop tree
// will be initialized there), meaning the below will only ever be
// executed on the client.
this.apollo = initApollo(this.props.serverState, {
getToken: () => parseCookies().token
})
}
render() {
return (
<ApolloProvider client={this.apollo}>
<ComposedComponent {...this.props} />
</ApolloProvider>
)
}
}
}
\ No newline at end of file
......@@ -6,8 +6,7 @@
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"test": "NODE_ENV=test ava"
"start": "next start"
},
"dependencies": {
"apollo-boost": "^0.1.4",
......@@ -20,16 +19,5 @@
"react": "^16.2.0",
"react-apollo": "^2.1.1",
"react-dom": "^16.2.0"
},
"devDependencies": {
"ava": "^0.19.1",
"clear-require": "^2.0.0",
"glob": "^7.1.2"
},
"ava": {
"require": [
"babel-register"
],
"babel": "inherit"
}
}
import App, { Container } from 'next/app'
import React from 'react'
import { ApolloProvider } from 'react-apollo'
import withApollo from '../lib/withApollo'
class MyApp extends App {
render () {
const { Component, pageProps, apolloClient } = this.props
return <Container>
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
</Container>
}
}
export default withApollo(MyApp)
import React from 'react'
import { compose } from 'react-apollo'
import Link from 'next/link'
import withData from '../lib/withData'
import redirect from '../lib/redirect'
import checkLoggedIn from '../lib/checkLoggedIn'
import RegisterBox from '../components/RegisterBox'
class CreateAccount extends React.Component {
static async getInitialProps (context, apolloClient) {
const { loggedInUser } = await checkLoggedIn(context, apolloClient)
export default class CreateAccount extends React.Component {
static async getInitialProps (context) {
const { loggedInUser } = await checkLoggedIn(context.apolloClient)
if (loggedInUser.user) {
// Already signed in? No need to continue.
......@@ -32,8 +30,3 @@ class CreateAccount extends React.Component {
)
}
};
export default compose( // TODO: Maybe remove the usage of compose?
// withData gives us server-side graphql queries before rendering
withData
)(CreateAccount)
import React from 'react'
import cookie from 'cookie'
import { withApollo, compose } from 'react-apollo'
import { ApolloConsumer } from 'react-apollo'
import withData from '../lib/withData'
import redirect from '../lib/redirect'
import checkLoggedIn from '../lib/checkLoggedIn'
class Index extends React.Component {
export default class Index extends React.Component {
static async getInitialProps (context, apolloClient) {
const { loggedInUser } = await checkLoggedIn(context, apolloClient)
const { loggedInUser } = await checkLoggedIn(context.apolloClient)
if (!loggedInUser.user) {
// If not signed in, send them somewhere more useful
......@@ -18,14 +17,14 @@ class Index extends React.Component {
return { loggedInUser }
}
signout = () => {
signout = apolloClient => () => {
document.cookie = cookie.serialize('token', '', {
maxAge: -1 // Expire the cookie immediately
})
// Force a reload of all the current queries now that the user is
// logged in, so we don't accidentally leave any state around.
this.props.client.cache.reset().then(() => {
apolloClient.cache.reset().then(() => {
// Redirect to a more useful page when signed out
redirect({}, '/signin')
})
......@@ -33,17 +32,14 @@ class Index extends React.Component {
render () {
return (
<div>
Hello {this.props.loggedInUser.user.name}!<br />
<button onClick={this.signout}>Sign out</button>
</div>
<ApolloConsumer>
{client => (
<div>
Hello {this.props.loggedInUser.user.name}!<br />
<button onClick={this.signout(client)}>Sign out</button>
</div>
)}
</ApolloConsumer>
)
}
};
export default compose(
// withData gives us server-side graphql queries before rendering
withData,
// withApollo exposes `this.props.client` used when logging out
withApollo
)(Index)
import React from 'react'
import { compose } from 'react-apollo'
import Link from 'next/link'
import withData from '../lib/withData'
import redirect from '../lib/redirect'
import checkLoggedIn from '../lib/checkLoggedIn'
import SigninBox from '../components/SigninBox'
class Signin extends React.Component {
static async getInitialProps (context, apolloClient) {
const { loggedInUser } = await checkLoggedIn(context, apolloClient)
export default class Signin extends React.Component {
static async getInitialProps (context) {
const { loggedInUser } = await checkLoggedIn(context.apolloClient)
if (loggedInUser.user) {
// Already signed in? No need to continue.
......@@ -32,8 +30,3 @@ class Signin extends React.Component {
)
}
};
export default compose( // TODO: Maybe remove the usage of compose?
// withData gives us server-side graphql queries before rendering
withData
)(Signin)
const clearRequire = require('clear-require')
const glob = require('glob')
const test = require('ava')
/**
* Motivations:
*
* - Client-side getInitialProps() wont have access to the apollo client for
* that page (because it's not shared across page bundles), so wont be able to
* reset the state, leaving all the logged in user data there :(
* - So, we have to have a shared module. BUT; next's code splitting means the
* bundle for each page will include its own copy of the module, _unless it's
* included in every page_.
* - https://github.com/zeit/next.js/issues/659#issuecomment-271824223
* - https://github.com/zeit/next.js/issues/1635#issuecomment-292236785
* - Therefore, this test ensures that every page includes that module, and
* hence it will be shared across every page, giving us a global store in
* Apollo that we can clear, etc
*/
const apolloFilePath = require.resolve('../lib/initApollo')
test.beforeEach(() => {
// Clean up the cache
clearRequire.all()
})
glob.sync('./pages/**/*.js').forEach((file) => {
test(`.${file} imports shared apollo module`, (t) => {
t.falsy(require.cache[apolloFilePath])
try {
require(`.${file}`)
} catch (error) {
// Don't really care if it fails to execute, etc, just want to be
// certain the expected require call was made
}
t.truthy(require.cache[apolloFilePath])
})
})
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册