mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Example updated: with-apollo-auth to Next 6 (#4420)
This commit is contained in:
parent
db545d9bc9
commit
915673fcd6
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"development": {
|
||||
"presets": "next/babel"
|
||||
},
|
||||
"production": {
|
||||
"presets": "next/babel"
|
||||
},
|
||||
"test": {
|
||||
"presets": [
|
||||
["env", { "modules": "commonjs" }],
|
||||
"next/babel"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
80
examples/with-apollo-auth/.gitignore
vendored
80
examples/with-apollo-auth/.gitignore
vendored
|
@ -1,80 +0,0 @@
|
|||
.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`.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import gql from 'graphql-tag'
|
||||
|
||||
export default (context, apolloClient) => (
|
||||
export default apolloClient => (
|
||||
apolloClient.query({
|
||||
query: gql`
|
||||
query getUser {
|
||||
|
|
97
examples/with-apollo-auth/lib/withApollo.js
Normal file
97
examples/with-apollo-auth/lib/withApollo.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
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} />
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
17
examples/with-apollo-auth/pages/_app.js
Normal file
17
examples/with-apollo-auth/pages/_app.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
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)
|
|
@ -1,16 +1,14 @@
|
|||
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)
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
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)
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
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)
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
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])
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue