♻️ spaces

This commit is contained in:
Sergey Yarkov 2020-03-02 22:41:26 +03:00
parent aff870be16
commit c5fb489720
12 changed files with 1772 additions and 217 deletions

View File

@ -7,6 +7,6 @@ const ARTICLES_QUERY = gql`
title title
} }
} }
`; `
export default ARTICLES_QUERY export default ARTICLES_QUERY

View File

@ -2,25 +2,25 @@ import Link from 'next/link'
import classes from './Header.module.scss' import classes from './Header.module.scss'
const Header = () => ( const Header = () => (
<header className={classes.Header}> <header className={classes.Header}>
<ul> <ul>
<li> <li>
<Link href='/'> <Link href='/'>
<a>Index</a> <a>Index</a>
</Link> </Link>
</li> </li>
<li> <li>
<Link href='/about'> <Link href='/about'>
<a>About</a> <a>About</a>
</Link> </Link>
</li> </li>
<li> <li>
<Link href='/articles'> <Link href='/articles'>
<a>Articles</a> <a>Articles</a>
</Link> </Link>
</li> </li>
</ul> </ul>
</header> </header>
) )
export default Header export default Header

View File

@ -1,14 +1,14 @@
import Header from '../Header/Header' import Header from '../Header/Header'
const Layout = props => { const Layout = props => {
return ( return (
<> <>
<Header /> <Header />
<main> <main>
{props.children} {props.children}
</main> </main>
</> </>
) )
} }
export default Layout export default Layout

View File

@ -1,17 +1,19 @@
import { useQuery } from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
const Query = ({ children, query, id }) => { const Query = ({ children, query, id }) => {
const { data, loading, error } = useQuery(query, { const { data, loading, error } = useQuery(query, {
variables: {id} variables: { id }
}) })
if (loading) return ( if (loading) {
<div className="uk-container"> return (
<p>loading...</p> <div className="uk-container">
</div> <p>loading...</p>
</div>
) )
if (error) return <p>Error: {JSON.stringify(error)}</p> }
return children({ data }) if (error) return <p>Error: {JSON.stringify(error)}</p>
return children({ data })
} }
export default Query export default Query

View File

@ -5,13 +5,13 @@ import Head from 'next/head'
*/ */
const SEO = props => { const SEO = props => {
const { pageTitle } = props const { pageTitle } = props
return ( return (
<Head> <Head>
<title>{pageTitle}</title> <title>{pageTitle}</title>
</Head> </Head>
) )
} }
export default SEO export default SEO

View File

@ -6,9 +6,9 @@ import fetch from 'isomorphic-unfetch'
const GRAPHQL_URL = process.env.BACKEND_URL || 'http://localhost:3000/api/graphql' const GRAPHQL_URL = process.env.BACKEND_URL || 'http://localhost:3000/api/graphql'
let apolloClient = null let apolloClient = null
/** /**
* Creates and provides the apolloContext * Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping * to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern. * your PageComponent via HOC pattern.
@ -16,117 +16,117 @@ const GRAPHQL_URL = process.env.BACKEND_URL || 'http://localhost:3000/api/graphq
* @param {Object} [config] * @param {Object} [config]
* @param {Boolean} [config.ssr=true] * @param {Boolean} [config.ssr=true]
*/ */
export function withApollo (PageComponent, { ssr = true } = {}) { export function withApollo (PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => { const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = useMemo( const client = useMemo(
() => apolloClient || initApolloClient(apolloState), () => apolloClient || initApolloClient(apolloState),
[] []
) )
return ( return (
<ApolloProvider client={client}> <ApolloProvider client={client}>
<PageComponent {...pageProps} /> <PageComponent {...pageProps} />
</ApolloProvider> </ApolloProvider>
) )
} }
// Set the correct displayName in development // Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
const displayName = const displayName =
PageComponent.displayName || PageComponent.name || 'Component' PageComponent.displayName || PageComponent.name || 'Component'
if (displayName === 'App') { if (displayName === 'App') {
console.warn('This withApollo HOC only works with PageComponents.') console.warn('This withApollo HOC only works with PageComponents.')
}
WithApollo.displayName = `withApollo(${displayName})`
} }
// Allow Next.js to remove getInitialProps from the browser build WithApollo.displayName = `withApollo(${displayName})`
if (typeof window === 'undefined') { }
if (ssr) {
WithApollo.getInitialProps = async ctx => { // Allow Next.js to remove getInitialProps from the browser build
const { AppTree } = ctx if (typeof window === 'undefined') {
if (ssr) {
let pageProps = {} WithApollo.getInitialProps = async ctx => {
if (PageComponent.getInitialProps) { const { AppTree } = ctx
pageProps = await PageComponent.getInitialProps(ctx)
} let pageProps = {}
if (PageComponent.getInitialProps) {
// Run all GraphQL queries in the component tree pageProps = await PageComponent.getInitialProps(ctx)
// and extract the resulting data }
const apolloClient = initApolloClient()
// Run all GraphQL queries in the component tree
try { // and extract the resulting data
// Run all GraphQL queries const apolloClient = initApolloClient()
await require('@apollo/react-ssr').getDataFromTree(
<AppTree try {
pageProps={{ // Run all GraphQL queries
...pageProps, await require('@apollo/react-ssr').getDataFromTree(
apolloClient <AppTree
}} pageProps={{
/> ...pageProps,
) apolloClient
} 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 } catch (error) {
console.error('Error while running `getDataFromTree`', 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
// getDataFromTree does not call componentWillUnmount console.error('Error while running `getDataFromTree`', error)
// head side effect therefore need to be cleared manually }
Head.rewind()
// getDataFromTree does not call componentWillUnmount
// Extract query data from the Apollo store // head side effect therefore need to be cleared manually
const apolloState = apolloClient.cache.extract() Head.rewind()
return { // Extract query data from the Apollo store
...pageProps, const apolloState = apolloClient.cache.extract()
apolloState
} return {
...pageProps,
apolloState
} }
} }
} }
return WithApollo
} }
/** return WithApollo
}
/**
* Always creates a new apollo client on the server * Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser. * Creates or reuses apollo client in the browser.
* @param {Object} initialState * @param {Object} initialState
*/ */
function initApolloClient (initialState) { function initApolloClient (initialState) {
// Make sure to create a new client for every server-side request so that data // Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad) // isn't shared between connections (which would be bad)
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return createApolloClient(initialState) return createApolloClient(initialState)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState)
}
return apolloClient
} }
/** // Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState)
}
return apolloClient
}
/**
* Creates and configures the ApolloClient * Creates and configures the ApolloClient
* @param {Object} [initialState={}] * @param {Object} [initialState={}]
*/ */
function createApolloClient (initialState = {}) { function createApolloClient (initialState = {}) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
const isBrowser = typeof window !== 'undefined' const isBrowser = typeof window !== 'undefined'
return new ApolloClient({ return new ApolloClient({
connectToDevTools: isBrowser, connectToDevTools: isBrowser,
ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once) ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({ link: new HttpLink({
uri: GRAPHQL_URL, // Server URL (must be absolute) uri: GRAPHQL_URL, // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers` credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
// Use fetch() polyfill on the server // Use fetch() polyfill on the server
fetch: !isBrowser && fetch fetch: !isBrowser && fetch
}), }),
cache: new InMemoryCache().restore(initialState) cache: new InMemoryCache().restore(initialState)
}) })
} }

1556
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,5 +21,15 @@
"optimize-css-assets-webpack-plugin": "^5.0.3", "optimize-css-assets-webpack-plugin": "^5.0.3",
"react": "16.13.0", "react": "16.13.0",
"react-dom": "16.13.0" "react-dom": "16.13.0"
},
"devDependencies": {
"eslint": "^6.8.0",
"eslint-config-node": "^4.0.0",
"eslint-config-standard-jsx": "^8.1.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.18.3",
"eslint-plugin-standard": "^4.0.1"
} }
} }

View File

@ -1,5 +1,5 @@
import '../assets/css/global.scss' import '../assets/css/global.scss'
export default function Application({ Component, pageProps }) { export default function Application ({ Component, pageProps }) {
return <Component {...pageProps} /> return <Component {...pageProps} />
} }

View File

@ -1,17 +1,17 @@
import Document, { Html, Head, Main, NextScript } from 'next/document' import Document, { Html, Head, Main, NextScript } from 'next/document'
class CustomDocument extends Document { class CustomDocument extends Document {
render() { render () {
return ( return (
<Html lang='en'> <Html lang='en'>
<Head /> <Head />
<body> <body>
<Main /> <Main />
<NextScript /> <NextScript />
</body> </body>
</Html> </Html>
) )
} }
} }
export default CustomDocument export default CustomDocument

View File

@ -1,11 +1,11 @@
import { gql, ApolloServer } from 'apollo-server-micro' import { gql, ApolloServer } from 'apollo-server-micro'
const Articles = [ const Articles = [
{id: 1, title: 'Article 1'}, { id: 1, title: 'Article 1' },
{id: 2, title: 'Article 2'}, { id: 2, title: 'Article 2' },
{id: 3, title: 'Article 3'}, { id: 3, title: 'Article 3' },
{id: 4, title: 'Article 4'}, { id: 4, title: 'Article 4' },
{id: 5, title: 'Article 5'} { id: 5, title: 'Article 5' }
] ]
const typeDefs = gql` const typeDefs = gql`
@ -17,27 +17,16 @@ const typeDefs = gql`
id: ID id: ID
title: String title: String
} }
`; `
const resolvers = { const resolvers = {
Query: { Query: {
articles: (_parent, _args, _context) => Articles articles: (_parent, _args, _context) => Articles
} }
};
const apolloServer = new ApolloServer({
typeDefs,
resolvers
});
const handler = apolloServer.createHandler({
path: "/api/graphql"
});
export const config = {
api: {
bodyParser: false
}
} }
export default handler const apolloServer = new ApolloServer({ typeDefs, resolvers })
const handler = apolloServer.createHandler({ path: '/api/graphql' })
export const config = { api: { bodyParser: false } }
export default handler

View File

@ -1,36 +1,34 @@
import Layout from '../components/Layout/Layout' import Layout from '../components/Layout/Layout'
import SEO from '../components/SEO/SEO' import SEO from '../components/SEO/SEO'
import Query from '../components/Query/query' import Query from '../components/Query/query'
import ARTICLES_QUERY from '../apollo/queries/articles/articles'; import ARTICLES_QUERY from '../apollo/queries/articles/articles'
import { withApollo } from '../lib/apollo'; import { withApollo } from '../lib/apollo'
const Articles = () => { const Articles = () => {
return ( return (
<> <>
<SEO pageTitle='Articles page' /> <SEO pageTitle='Articles page' />
<Layout> <Layout>
<h1>Articles page</h1> <h1>Articles page</h1>
<div> <div>
<Query query={ARTICLES_QUERY}> <Query query={ARTICLES_QUERY}>
{({ data: { articles } }) => { {({ data: { articles } }) => {
return articles.map(art => { return articles.map(art => {
return ( return (
<div key={art.id}> <div key={art.id}>
<p> <p>
title: {art.title} <br /> title: {art.title} <br />
id: {art.id} id: {art.id}
</p> </p>
</div> </div>
) )
}) })
}} }}
</Query> </Query>
</div> </div>
</Layout> </Layout>
</> </>
) )
} }
export default withApollo(Articles) export default withApollo(Articles)