From 915673fcd69e120084c14582fd80f2fc7140c147 Mon Sep 17 00:00:00 2001 From: Luis Fernando Alvarez D Date: Fri, 18 May 2018 03:55:12 -0500 Subject: [PATCH] Example updated: with-apollo-auth to Next 6 (#4420) --- examples/with-apollo-auth/.babelrc | 16 --- examples/with-apollo-auth/.gitignore | 80 -------------- examples/with-apollo-auth/README.md | 2 +- .../with-apollo-auth/lib/checkLoggedIn.js | 2 +- examples/with-apollo-auth/lib/withApollo.js | 97 +++++++++++++++++ examples/with-apollo-auth/lib/withData.js | 103 ------------------ examples/with-apollo-auth/package.json | 14 +-- examples/with-apollo-auth/pages/_app.js | 17 +++ .../with-apollo-auth/pages/create-account.js | 13 +-- examples/with-apollo-auth/pages/index.js | 30 +++-- examples/with-apollo-auth/pages/signin.js | 13 +-- .../with-apollo-auth/test/shared-apollo.js | 41 ------- 12 files changed, 136 insertions(+), 292 deletions(-) delete mode 100644 examples/with-apollo-auth/.babelrc delete mode 100644 examples/with-apollo-auth/.gitignore create mode 100644 examples/with-apollo-auth/lib/withApollo.js delete mode 100644 examples/with-apollo-auth/lib/withData.js create mode 100644 examples/with-apollo-auth/pages/_app.js delete mode 100644 examples/with-apollo-auth/test/shared-apollo.js diff --git a/examples/with-apollo-auth/.babelrc b/examples/with-apollo-auth/.babelrc deleted file mode 100644 index ca67af2f7a..0000000000 --- a/examples/with-apollo-auth/.babelrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "env": { - "development": { - "presets": "next/babel" - }, - "production": { - "presets": "next/babel" - }, - "test": { - "presets": [ - ["env", { "modules": "commonjs" }], - "next/babel" - ] - } - } -} diff --git a/examples/with-apollo-auth/.gitignore b/examples/with-apollo-auth/.gitignore deleted file mode 100644 index 4ed44b15db..0000000000 --- a/examples/with-apollo-auth/.gitignore +++ /dev/null @@ -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 diff --git a/examples/with-apollo-auth/README.md b/examples/with-apollo-auth/README.md index 281002f98b..0ba866434c 100644 --- a/examples/with-apollo-auth/README.md +++ b/examples/with-apollo-auth/README.md @@ -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`. diff --git a/examples/with-apollo-auth/lib/checkLoggedIn.js b/examples/with-apollo-auth/lib/checkLoggedIn.js index ee3d3933b2..29f198ba2e 100644 --- a/examples/with-apollo-auth/lib/checkLoggedIn.js +++ b/examples/with-apollo-auth/lib/checkLoggedIn.js @@ -1,6 +1,6 @@ import gql from 'graphql-tag' -export default (context, apolloClient) => ( +export default apolloClient => ( apolloClient.query({ query: gql` query getUser { diff --git a/examples/with-apollo-auth/lib/withApollo.js b/examples/with-apollo-auth/lib/withApollo.js new file mode 100644 index 0000000000..a51015660f --- /dev/null +++ b/examples/with-apollo-auth/lib/withApollo.js @@ -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( + + ) + } 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 + } + } +} diff --git a/examples/with-apollo-auth/lib/withData.js b/examples/with-apollo-auth/lib/withData.js deleted file mode 100644 index fcec71ba6b..0000000000 --- a/examples/with-apollo-auth/lib/withData.js +++ /dev/null @@ -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 = ( - - - - ) - 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 ( - - - - ) - } - } -} \ No newline at end of file diff --git a/examples/with-apollo-auth/package.json b/examples/with-apollo-auth/package.json index b967968609..d783be4571 100644 --- a/examples/with-apollo-auth/package.json +++ b/examples/with-apollo-auth/package.json @@ -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" } } diff --git a/examples/with-apollo-auth/pages/_app.js b/examples/with-apollo-auth/pages/_app.js new file mode 100644 index 0000000000..bf3894cca7 --- /dev/null +++ b/examples/with-apollo-auth/pages/_app.js @@ -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 + + + + + } +} + +export default withApollo(MyApp) diff --git a/examples/with-apollo-auth/pages/create-account.js b/examples/with-apollo-auth/pages/create-account.js index a5b59d3d45..8894c70ba8 100644 --- a/examples/with-apollo-auth/pages/create-account.js +++ b/examples/with-apollo-auth/pages/create-account.js @@ -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) diff --git a/examples/with-apollo-auth/pages/index.js b/examples/with-apollo-auth/pages/index.js index eb1fd6f35e..6b979f76ef 100644 --- a/examples/with-apollo-auth/pages/index.js +++ b/examples/with-apollo-auth/pages/index.js @@ -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 ( -
- Hello {this.props.loggedInUser.user.name}!
- -
+ + {client => ( +
+ Hello {this.props.loggedInUser.user.name}!
+ +
+ )} +
) } }; - -export default compose( - // withData gives us server-side graphql queries before rendering - withData, - // withApollo exposes `this.props.client` used when logging out - withApollo -)(Index) diff --git a/examples/with-apollo-auth/pages/signin.js b/examples/with-apollo-auth/pages/signin.js index e0397651bb..65cb5aa3f1 100644 --- a/examples/with-apollo-auth/pages/signin.js +++ b/examples/with-apollo-auth/pages/signin.js @@ -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) diff --git a/examples/with-apollo-auth/test/shared-apollo.js b/examples/with-apollo-auth/test/shared-apollo.js deleted file mode 100644 index 07e6451e96..0000000000 --- a/examples/with-apollo-auth/test/shared-apollo.js +++ /dev/null @@ -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]) - }) -}) -- GitLab