From d763222693871393b7f81f30ce29637940167681 Mon Sep 17 00:00:00 2001 From: Aaron Reisman Date: Thu, 2 Jan 2020 17:25:39 -0800 Subject: [PATCH 1/3] Add Apollo Server and Client Auth Example --- .../README.md | 49 ++++++ .../apollo/client.js | 152 ++++++++++++++++++ .../apollo/resolvers.js | 95 +++++++++++ .../apollo/schema.js | 8 + .../apollo/type-defs.js | 38 +++++ .../components/field.js | 22 +++ .../lib/form.js | 15 ++ .../next.config.js | 5 + .../package.json | 32 ++++ .../pages/about.js | 11 ++ .../pages/api/graphql.js | 17 ++ .../pages/index.js | 46 ++++++ .../pages/signin.js | 75 +++++++++ .../pages/signout.js | 29 ++++ .../pages/signup.js | 73 +++++++++ 15 files changed, 667 insertions(+) create mode 100644 examples/api-routes-apollo-server-and-client-auth/README.md create mode 100644 examples/api-routes-apollo-server-and-client-auth/apollo/client.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/apollo/resolvers.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/apollo/schema.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/apollo/type-defs.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/components/field.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/lib/form.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/next.config.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/package.json create mode 100644 examples/api-routes-apollo-server-and-client-auth/pages/about.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/pages/api/graphql.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/pages/index.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/pages/signin.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/pages/signout.js create mode 100644 examples/api-routes-apollo-server-and-client-auth/pages/signup.js diff --git a/examples/api-routes-apollo-server-and-client-auth/README.md b/examples/api-routes-apollo-server-and-client-auth/README.md new file mode 100644 index 000000000000000..3030c67d2af6d8b --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/README.md @@ -0,0 +1,49 @@ +# Apollo Server and Client Auth Example + +## How to use + +### Using `create-next-app` + +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: + +```bash +npx create-next-app --example api-routes-apollo-server-and-client-auth api-routes-apollo-server-and-client-auth-app +# or +yarn create next-app --example api-routes-apollo-server-and-client-auth api-routes-apollo-server-and-client-auth-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/api-routes-apollo-server-and-client-auth +cd api-routes-apollo-server-and-client-auth +``` + +Install it and run: + +```bash +npm install +npm run dev +# or +yarn +yarn dev +``` + +Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)): + +```bash +now +``` + +## The idea behind the example + +[Apollo](https://www.apollographql.com/client/) is a GraphQL client that allows you to easily query the exact data you need from a GraphQL server. In addition to fetching and mutating data, Apollo analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run, fetching more results from the server. + +In this simple example, we integrate Apollo seamlessly with Next by wrapping our _pages/\_app.js_ 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 central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application. + +On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized. + +Note: Do not be alarmed that you see two renders being executed. Apollo recursively traverses the React render tree looking for Apollo query components. When it has done that, it fetches all these queries and then passes the result to a cache. This cache is then used to render the data on the server side (another React render). +https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree diff --git a/examples/api-routes-apollo-server-and-client-auth/apollo/client.js b/examples/api-routes-apollo-server-and-client-auth/apollo/client.js new file mode 100644 index 000000000000000..5e820d7596b866d --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/apollo/client.js @@ -0,0 +1,152 @@ +import React from 'react' +import Head from 'next/head' +import { ApolloProvider } from '@apollo/react-hooks' +import { ApolloClient } from 'apollo-client' +import { InMemoryCache } from 'apollo-cache-inmemory' + +let apolloClient = null + +/** + * Creates and provides the apolloContext + * to a next.js PageTree. Use it by wrapping + * your PageComponent via HOC pattern. + * @param {Function|Class} PageComponent + * @param {Object} [config] + * @param {Boolean} [config.ssr=true] + */ +export function withApollo(PageComponent, { ssr = true } = {}) { + const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => { + const client = apolloClient || initApolloClient(undefined, apolloState) + return ( + + + + ) + } + + // Set the correct displayName in development + if (process.env.NODE_ENV !== 'production') { + const displayName = + PageComponent.displayName || PageComponent.name || 'Component' + + if (displayName === 'App') { + console.warn('This withApollo HOC only works with PageComponents.') + } + + WithApollo.displayName = `withApollo(${displayName})` + } + + if (ssr || PageComponent.getInitialProps) { + WithApollo.getInitialProps = async ctx => { + const { AppTree } = ctx + + // Initialize ApolloClient, add it to the ctx object so + // we can use it in `PageComponent.getInitialProp`. + const apolloClient = (ctx.apolloClient = initApolloClient({ + res: ctx.res, + req: ctx.req, + })) + + // Run wrapped getInitialProps methods + let pageProps = {} + if (PageComponent.getInitialProps) { + pageProps = await PageComponent.getInitialProps(ctx) + } + + // Only on the server: + if (typeof window === 'undefined') { + // When redirecting, the response is finished. + // No point in continuing to render + if (ctx.res && ctx.res.finished) { + return pageProps + } + + // Only if ssr is enabled + if (ssr) { + try { + // Run all GraphQL queries + const { getDataFromTree } = await import('@apollo/react-ssr') + await getDataFromTree( + + ) + } catch (error) { + // Prevent Apollo Client GraphQL errors from crashing SSR. + // Handle them in components via the data.error prop: + // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error + console.error('Error while running `getDataFromTree`', error) + } + + // getDataFromTree does not call componentWillUnmount + // head side effect therefore need to be cleared manually + Head.rewind() + } + } + + // Extract query data from the Apollo store + const apolloState = apolloClient.cache.extract() + + return { + ...pageProps, + apolloState, + } + } + } + + return WithApollo +} + +/** + * Always creates a new apollo client on the server + * Creates or reuses apollo client in the browser. + * @param {Object} initialState + */ +function initApolloClient(ctx, initialState) { + // Make sure to create a new client for every server-side request so that data + // isn't shared between connections (which would be bad) + if (typeof window === 'undefined') { + return createApolloClient(ctx, initialState) + } + + // Reuse client on the client-side + if (!apolloClient) { + apolloClient = createApolloClient(ctx, initialState) + } + + return apolloClient +} + +/** + * Creates and configures the ApolloClient + * @param {Object} [initialState={}] + */ +function createApolloClient(ctx = {}, initialState = {}) { + const ssrMode = typeof window === 'undefined' + const cache = new InMemoryCache().restore(initialState) + + // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient + return new ApolloClient({ + ssrMode, + link: createIsomorphLink(ctx), + cache, + }) +} + +function createIsomorphLink(ctx) { + if (typeof window === 'undefined') { + const { SchemaLink } = require('apollo-link-schema') + const { schema } = require('./schema') + return new SchemaLink({ schema, context: ctx }) + } else { + const { HttpLink } = require('apollo-link-http') + + return new HttpLink({ + uri: '/api/graphql', + credentials: 'same-origin', + }) + } +} diff --git a/examples/api-routes-apollo-server-and-client-auth/apollo/resolvers.js b/examples/api-routes-apollo-server-and-client-auth/apollo/resolvers.js new file mode 100644 index 000000000000000..76e6d851cdba0c0 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/apollo/resolvers.js @@ -0,0 +1,95 @@ +import { AuthenticationError, UserInputError } from 'apollo-server-micro' +import cookie from 'cookie' +import jwt from 'jsonwebtoken' +import getConfig from 'next/config' +import bcrypt from 'bcrypt' +import v4 from 'uuid/v4' + +const JWT_SECRET = getConfig().serverRuntimeConfig.JWT_SECRET + +const users = [] + +function createUser(data) { + const salt = bcrypt.genSaltSync() + + return { + id: v4(), + email: data.email, + hashedPassword: bcrypt.hashSync(data.password, salt), + } +} + +function validPassword(user, password) { + return bcrypt.compareSync(password, user.hashedPassword) +} + +export const resolvers = { + Query: { + async viewer(_parent, _args, context, _info) { + const { token } = cookie.parse(context.req.headers.cookie ?? '') + if (token) { + try { + const { id, email } = jwt.verify(token, JWT_SECRET) + + return users.find(user => user.id === id && user.email === email) + } catch { + throw new AuthenticationError( + 'Authentication token is invalid, please log in' + ) + } + } + }, + }, + Mutation: { + async signUp(_parent, args, _context, _info) { + const user = createUser(args.input) + + users.push(user) + + return { user } + }, + + async signIn(_parent, args, context, _info) { + const user = users.find(user => user.email === args.input.email) + + if (user && validPassword(user, args.input.password)) { + const token = jwt.sign( + { email: user.email, id: user.id, time: new Date() }, + JWT_SECRET, + { + expiresIn: '6h', + } + ) + + context.res.setHeader( + 'Set-Cookie', + cookie.serialize('token', token, { + httpOnly: true, + maxAge: 6 * 60 * 60, + path: '/', + sameSite: 'lax', + secure: process.env.NODE_ENV === 'production', + }) + ) + + return { user } + } + + throw new UserInputError('Invalid email and password combination') + }, + async signOut(_parent, _args, context, _info) { + context.res.setHeader( + 'Set-Cookie', + cookie.serialize('token', '', { + httpOnly: true, + maxAge: -1, + path: '/', + sameSite: 'lax', + secure: process.env.NODE_ENV === 'production', + }) + ) + + return true + }, + }, +} diff --git a/examples/api-routes-apollo-server-and-client-auth/apollo/schema.js b/examples/api-routes-apollo-server-and-client-auth/apollo/schema.js new file mode 100644 index 000000000000000..f6d70b7e86243cf --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/apollo/schema.js @@ -0,0 +1,8 @@ +import { makeExecutableSchema } from 'graphql-tools' +import { typeDefs } from './type-defs' +import { resolvers } from './resolvers' + +export const schema = makeExecutableSchema({ + typeDefs, + resolvers, +}) diff --git a/examples/api-routes-apollo-server-and-client-auth/apollo/type-defs.js b/examples/api-routes-apollo-server-and-client-auth/apollo/type-defs.js new file mode 100644 index 000000000000000..cd77f1e5b26e7d1 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/apollo/type-defs.js @@ -0,0 +1,38 @@ +import gql from 'graphql-tag' + +export const typeDefs = gql` + type User { + id: ID! + email: String! + } + + input SignUpInput { + email: String! + password: String! + } + + input SignInInput { + email: String! + password: String! + } + + type SignUpPayload { + user: User! + } + + type SignInPayload { + user: User! + } + + type Query { + user(id: ID!): User! + users: [User]! + viewer: User + } + + type Mutation { + signUp(input: SignUpInput!): SignUpPayload! + signIn(input: SignInInput!): SignInPayload! + signOut: Boolean! + } +` diff --git a/examples/api-routes-apollo-server-and-client-auth/components/field.js b/examples/api-routes-apollo-server-and-client-auth/components/field.js new file mode 100644 index 000000000000000..2fa688740b6efb2 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/components/field.js @@ -0,0 +1,22 @@ +export default function Field(props) { + return ( +
+ +
+ + {props.status ?

{props.status.message}

: undefined} +
+ ) +} diff --git a/examples/api-routes-apollo-server-and-client-auth/lib/form.js b/examples/api-routes-apollo-server-and-client-auth/lib/form.js new file mode 100644 index 000000000000000..d912cd8544ee358 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/lib/form.js @@ -0,0 +1,15 @@ +export function getStatusFrom(error) { + const validationErrors = {} + if (error.graphQLErrors) { + for (const graphQLError of error.graphQLErrors) { + if ( + graphQLError.extensions && + graphQLError.extensions.code === 'BAD_USER_INPUT' + ) { + return { '': graphQLError.message } + } + } + } + + return validationErrors +} diff --git a/examples/api-routes-apollo-server-and-client-auth/next.config.js b/examples/api-routes-apollo-server-and-client-auth/next.config.js new file mode 100644 index 000000000000000..35db685d842d224 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + serverRuntimeConfig: { + JWT_SECRET: 'changeme', + }, +} diff --git a/examples/api-routes-apollo-server-and-client-auth/package.json b/examples/api-routes-apollo-server-and-client-auth/package.json new file mode 100644 index 000000000000000..cc147e35e06fe6c --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/package.json @@ -0,0 +1,32 @@ +{ + "name": "with-apollo", + "version": "2.0.0", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "@apollo/react-common": "3.1.3", + "@apollo/react-hooks": "3.1.3", + "@apollo/react-ssr": "3.1.3", + "apollo-cache-inmemory": "1.6.5", + "apollo-client": "2.6.8", + "apollo-link-http": "1.5.16", + "apollo-link-schema": "1.2.4", + "apollo-server-micro": "2.9.16", + "apollo-utilities": "^1.3.2", + "bcrypt": "3.0.7", + "cookie": "0.4.0", + "graphql": "^14.0.2", + "graphql-tag": "2.10.1", + "jsonwebtoken": "8.5.1", + "next": "latest", + "prop-types": "^15.6.2", + "react": "^16.7.0", + "react-dom": "^16.7.0", + "uuid": "3.4.0" + }, + "author": "", + "license": "ISC" +} diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/about.js b/examples/api-routes-apollo-server-and-client-auth/pages/about.js new file mode 100644 index 000000000000000..37a11a9e09651f1 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/pages/about.js @@ -0,0 +1,11 @@ +import Link from 'next/link' + +export default () => ( +
+ This is a static page goto{' '} + + dynamic + {' '} + page. +
+) diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/api/graphql.js b/examples/api-routes-apollo-server-and-client-auth/pages/api/graphql.js new file mode 100644 index 000000000000000..1e85d8c07dde585 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/pages/api/graphql.js @@ -0,0 +1,17 @@ +import { ApolloServer } from 'apollo-server-micro' +import { schema } from '../../apollo/schema' + +const apolloServer = new ApolloServer({ + schema, + context(ctx) { + return ctx + }, +}) + +export const config = { + api: { + bodyParser: false, + }, +} + +export default apolloServer.createHandler({ path: '/api/graphql' }) diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/index.js b/examples/api-routes-apollo-server-and-client-auth/pages/index.js new file mode 100644 index 000000000000000..2ea42bf1f70f599 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/pages/index.js @@ -0,0 +1,46 @@ +import { withApollo } from '../apollo/client' +import gql from 'graphql-tag' +import Link from 'next/link' +import { useQuery } from '@apollo/react-hooks' +import { useRouter } from 'next/router' + +const ViewerQuery = gql` + query ViewerQuery { + viewer { + id + email + } + } +` + +const Index = () => { + const router = useRouter() + const { data, loading } = useQuery(ViewerQuery) + + if ( + loading === false && + data.viewer === null && + typeof window !== 'undefined' + ) { + router.push('/signin') + } + + if (data && data.viewer) { + return ( +
+ You're signed in as {data.viewer.email} goto{' '} + + static + {' '} + page. or{' '} + + signout + +
+ ) + } + + return

Loading...

+} + +export default withApollo(Index) diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/signin.js b/examples/api-routes-apollo-server-and-client-auth/pages/signin.js new file mode 100644 index 000000000000000..ff3ee977786ddc7 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/pages/signin.js @@ -0,0 +1,75 @@ +import React from 'react' +import Link from 'next/link' +import { withApollo } from '../apollo/client' +import gql from 'graphql-tag' +import { useMutation } from '@apollo/react-hooks' +import Field from '../components/field' +import { getStatusFrom } from '../lib/form' +import { useRouter } from 'next/router' + +const SignInMutation = gql` + mutation SignInMutation($email: String!, $password: String!) { + signIn(input: { email: $email, password: $password }) { + user { + id + email + } + } + } +` + +function SignIn() { + const [signIn] = useMutation(SignInMutation) + const [status, setStatus] = React.useState({}) + const router = useRouter() + async function handleSubmit(event) { + event.preventDefault() + const emailElement = event.currentTarget.elements.email + const passwordElement = event.currentTarget.elements.password + + try { + const { data } = await signIn({ + variables: { + email: emailElement.value, + password: passwordElement.value, + }, + }) + if (data.signIn.user) { + router.push('/') + } + } catch (error) { + setStatus(getStatusFrom(error)) + } + } + + return ( + <> +

Sign In

+
+ {'' in status ?

{status['']}

: undefined} + + + or{' '} + + Sign up + + + + ) +} + +export default withApollo(SignIn) diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/signout.js b/examples/api-routes-apollo-server-and-client-auth/pages/signout.js new file mode 100644 index 000000000000000..7a058862257e37c --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/pages/signout.js @@ -0,0 +1,29 @@ +import React from 'react' +import { useMutation } from '@apollo/react-hooks' + +import gql from 'graphql-tag' +import { useRouter } from 'next/router' +import { withApollo } from '../apollo/client' + +const SignOutMutation = gql` + mutation SignOutMutation { + signOut + } +` + +function SignOut() { + const router = useRouter() + const [signOut] = useMutation(SignOutMutation) + + React.useEffect(() => { + if (typeof window !== 'undefined') { + signOut().then(() => { + router.push('/signin') + }) + } + }, [signOut, router]) + + return

Signing out...

+} + +export default withApollo(SignOut) diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/signup.js b/examples/api-routes-apollo-server-and-client-auth/pages/signup.js new file mode 100644 index 000000000000000..b792d5c8fff5962 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client-auth/pages/signup.js @@ -0,0 +1,73 @@ +import React from 'react' +import Link from 'next/link' +import { withApollo } from '../apollo/client' +import gql from 'graphql-tag' +import { useMutation } from '@apollo/react-hooks' +import Field from '../components/field' +import { getStatusFrom } from '../lib/form' +import { useRouter } from 'next/router' + +const SignUpMutation = gql` + mutation SignUpMutation($email: String!, $password: String!) { + signUp(input: { email: $email, password: $password }) { + user { + id + email + } + } + } +` + +function SignUp() { + const [signUp] = useMutation(SignUpMutation) + const [status, setStatus] = React.useState({}) + const router = useRouter() + async function handleSubmit(event) { + event.preventDefault() + const emailElement = event.currentTarget.elements.email + const passwordElement = event.currentTarget.elements.password + + try { + await signUp({ + variables: { + email: emailElement.value, + password: passwordElement.value, + }, + }) + + router.push('/signin') + } catch (error) { + setStatus(getStatusFrom(error)) + } + } + + return ( + <> +

Sign Up

+
+ + + or{' '} + + Sign in + + + + ) +} + +export default withApollo(SignUp) From e9358543daae4883431f045e4329e77b3bedd2c3 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Thu, 23 Jan 2020 14:52:28 -0500 Subject: [PATCH 2/3] Some updates --- .../apollo/client.js | 8 ++++---- .../components/field.js | 1 - .../lib/form.js | 8 +++----- .../pages/signin.js | 12 ++++++------ .../pages/signup.js | 10 +++++----- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/examples/api-routes-apollo-server-and-client-auth/apollo/client.js b/examples/api-routes-apollo-server-and-client-auth/apollo/client.js index 5e820d7596b866d..55135ad4eb17cbe 100644 --- a/examples/api-routes-apollo-server-and-client-auth/apollo/client.js +++ b/examples/api-routes-apollo-server-and-client-auth/apollo/client.js @@ -4,7 +4,7 @@ import { ApolloProvider } from '@apollo/react-hooks' import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' -let apolloClient = null +let globalApolloClient = null /** * Creates and provides the apolloContext @@ -113,11 +113,11 @@ function initApolloClient(ctx, initialState) { } // Reuse client on the client-side - if (!apolloClient) { - apolloClient = createApolloClient(ctx, initialState) + if (!globalApolloClient) { + globalApolloClient = createApolloClient(ctx, initialState) } - return apolloClient + return globalApolloClient } /** diff --git a/examples/api-routes-apollo-server-and-client-auth/components/field.js b/examples/api-routes-apollo-server-and-client-auth/components/field.js index 2fa688740b6efb2..5ad6fa2e3548e7f 100644 --- a/examples/api-routes-apollo-server-and-client-auth/components/field.js +++ b/examples/api-routes-apollo-server-and-client-auth/components/field.js @@ -16,7 +16,6 @@ export default function Field(props) { required={props.required} type={props.type} /> - {props.status ?

{props.status.message}

: undefined} ) } diff --git a/examples/api-routes-apollo-server-and-client-auth/lib/form.js b/examples/api-routes-apollo-server-and-client-auth/lib/form.js index d912cd8544ee358..7fef9d257db755c 100644 --- a/examples/api-routes-apollo-server-and-client-auth/lib/form.js +++ b/examples/api-routes-apollo-server-and-client-auth/lib/form.js @@ -1,15 +1,13 @@ -export function getStatusFrom(error) { - const validationErrors = {} +export function getErrorMessage(error) { if (error.graphQLErrors) { for (const graphQLError of error.graphQLErrors) { if ( graphQLError.extensions && graphQLError.extensions.code === 'BAD_USER_INPUT' ) { - return { '': graphQLError.message } + return graphQLError.message } } } - - return validationErrors + return error.message } diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/signin.js b/examples/api-routes-apollo-server-and-client-auth/pages/signin.js index ff3ee977786ddc7..04e7d140c4976e4 100644 --- a/examples/api-routes-apollo-server-and-client-auth/pages/signin.js +++ b/examples/api-routes-apollo-server-and-client-auth/pages/signin.js @@ -4,7 +4,7 @@ import { withApollo } from '../apollo/client' import gql from 'graphql-tag' import { useMutation } from '@apollo/react-hooks' import Field from '../components/field' -import { getStatusFrom } from '../lib/form' +import { getErrorMessage } from '../lib/form' import { useRouter } from 'next/router' const SignInMutation = gql` @@ -20,10 +20,12 @@ const SignInMutation = gql` function SignIn() { const [signIn] = useMutation(SignInMutation) - const [status, setStatus] = React.useState({}) + const [errorMsg, setErrorMsg] = React.useState() const router = useRouter() + async function handleSubmit(event) { event.preventDefault() + const emailElement = event.currentTarget.elements.email const passwordElement = event.currentTarget.elements.password @@ -38,7 +40,7 @@ function SignIn() { router.push('/') } } catch (error) { - setStatus(getStatusFrom(error)) + setErrorMsg(getErrorMessage(error)) } } @@ -46,14 +48,13 @@ function SignIn() { <>

Sign In

- {'' in status ?

{status['']}

: undefined} + {errorMsg &&

{errorMsg}

} or{' '} diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/signup.js b/examples/api-routes-apollo-server-and-client-auth/pages/signup.js index b792d5c8fff5962..77146b7759ba258 100644 --- a/examples/api-routes-apollo-server-and-client-auth/pages/signup.js +++ b/examples/api-routes-apollo-server-and-client-auth/pages/signup.js @@ -4,7 +4,7 @@ import { withApollo } from '../apollo/client' import gql from 'graphql-tag' import { useMutation } from '@apollo/react-hooks' import Field from '../components/field' -import { getStatusFrom } from '../lib/form' +import { getErrorMessage } from '../lib/form' import { useRouter } from 'next/router' const SignUpMutation = gql` @@ -20,8 +20,9 @@ const SignUpMutation = gql` function SignUp() { const [signUp] = useMutation(SignUpMutation) - const [status, setStatus] = React.useState({}) + const [errorMsg, setErrorMsg] = React.useState() const router = useRouter() + async function handleSubmit(event) { event.preventDefault() const emailElement = event.currentTarget.elements.email @@ -37,7 +38,7 @@ function SignUp() { router.push('/signin') } catch (error) { - setStatus(getStatusFrom(error)) + setErrorMsg(getErrorMessage(error)) } } @@ -45,13 +46,13 @@ function SignUp() { <>

Sign Up

+ {errorMsg &&

{errorMsg}

} or{' '} From f9c13a6a1b056bdc75962ecad9dfed6205586cb4 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Thu, 23 Jan 2020 14:52:44 -0500 Subject: [PATCH 3/3] Updated docs --- .../README.md | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/examples/api-routes-apollo-server-and-client-auth/README.md b/examples/api-routes-apollo-server-and-client-auth/README.md index 3030c67d2af6d8b..a449cf4c45ffb1e 100644 --- a/examples/api-routes-apollo-server-and-client-auth/README.md +++ b/examples/api-routes-apollo-server-and-client-auth/README.md @@ -1,5 +1,20 @@ # Apollo Server and Client Auth Example +[Apollo](https://www.apollographql.com/client/) is a GraphQL client that allows you to easily query the exact data you need from a GraphQL server. In addition to fetching and mutating data, Apollo analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run, fetching more results from the server. + +In this simple example, we integrate Apollo seamlessly with Next by wrapping our _pages/\_app.js_ 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 central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application. + +On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized. + +Note: Do not be alarmed that you see two renders being executed. Apollo recursively traverses the React render tree looking for Apollo query components. When it has done that, it fetches all these queries and then passes the result to a cache. This cache is then used to render the data on the server side (another React render). +https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree + +## Deploy your own + +Deploy the example using [ZEIT Now](https://zeit.co/now): + +[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/new/project?template=https://github.com/zeit/next.js/tree/canary/examples/api-routes-apollo-server-and-client-auth) + ## How to use ### Using `create-next-app` @@ -31,19 +46,10 @@ yarn yarn dev ``` +> If you have issues installing `bcrypt`, follow this instructions: https://github.com/kelektiv/node.bcrypt.js/wiki/Installation-Instructions + Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)): ```bash now ``` - -## The idea behind the example - -[Apollo](https://www.apollographql.com/client/) is a GraphQL client that allows you to easily query the exact data you need from a GraphQL server. In addition to fetching and mutating data, Apollo analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run, fetching more results from the server. - -In this simple example, we integrate Apollo seamlessly with Next by wrapping our _pages/\_app.js_ 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 central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application. - -On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized. - -Note: Do not be alarmed that you see two renders being executed. Apollo recursively traverses the React render tree looking for Apollo query components. When it has done that, it fetches all these queries and then passes the result to a cache. This cache is then used to render the data on the server side (another React render). -https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree