MonkeyBonkey
MonkeyBonkey

Reputation: 47861

Redux server side rendering breaks when using an apollo graphql client

When I'm using an apollo provider with redux server side rendering,

https://github.com/reactjs/redux/blob/master/docs/recipes/ServerRendering.md

I get the following warning and it breaks the server side output

Warning: Failed context type: The context `client` is marked as required in `Apollo(Home)`, but its value is `undefined`.
    in Apollo(Home) (created by Connect(Apollo(Home)))
    in Connect(Apollo(Home)) (created by RouterContext)
    in RouterContext
    in Provider

However this renders fine client side.

app

window.webappStart = () => {
  const initialState = window.__PRELOADED_STATE__;
  const store = createStore(rootReducer, initialState);

  const client = new ApolloClient({
    networkInterface: createNetworkInterface({ uri: 'https://api.graph.cool/simple/v1/foo' }),
  });

  render(
    <ApolloProvider store={store} client={client}>
      <Router>{routes}</Router>
    </ApolloProvider>,
    document.querySelector(".js-content")
  );
};

Here's the boilerplate apollo code

import React from 'react';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';

// The data prop, which is provided by the wrapper below contains,
// a `loading` key while the query is in flight and posts when it is ready
function PostList({ data: { loading, posts } }) {
  if (loading) {
    return <div>Loading</div>;
  } else {
    return (
      <ul>
        {posts.map(post =>
          <li key={post.id}>
            {post.title} by {' '}
            {post.author.firstName} {post.author.lastName} {' '}
            ({post.votes} votes)
          </li>
        )}
      </ul>
    );
  }
}

// The `graphql` wrapper executes a GraphQL query and makes the results
// available on the `data` prop of the wrapped component (PostList here)
export default graphql(gql`
  query allPosts {
    posts {
      id
      title
      votes
      author {
        id
        firstName
        lastName
      }
    }
  }
`)(PostList);

Upvotes: 2

Views: 635

Answers (1)

estrattonbailey
estrattonbailey

Reputation: 11

The PostList component looks alright to me, as does the client-side initiation of your app.

If you're getting that error in your server logs, then I think you'll want to check your routing middleware to ensure you're passing the client to ApolloProvider before rendering your app.

I'm using Express v4.* and react-router v4. My setup looks like this:

import React from 'react'
import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import ApolloClient, { createNetworkInterface } from 'apollo-client'
import { ApolloProvider, renderToStringWithData } from 'react-apollo'
import routes from '../app/routes.js'
import { store } from 'app/store/index.js'

const Html = ({ title = 'App', content }) => (
  <html>
    <head>
      <title>{title}</title>
      <link href="/main.css" rel="stylesheet"/>
    </head>
    <body>
      <div id="root" dangerouslySetInnerHTML={{ __html: content }} />
      <script src='/index.js'/>
    </body>
  </html>
)

module.exports = (req, res) => {
  match(
    {
      location: req.originalUrl,
      routes,
    },
    (error, redirectLocation, renderProps) => {
      if (redirectLocation) {
        res.redirect(redirectLocation.pathname + redirectLocation.search)
      } else if (error) {
        console.error('ROUTER ERROR:', error)
        res.status(500)
      } else if (renderProps) {
        const client = new ApolloClient({
          ssrMode: true,
          networkInterface: createNetworkInterface({
            uri: 'http://localhost:8888/graphql',
          }),
        })

        /**
         * Make sure client is added here. Store is optional */
        const App = (
          <ApolloProvider client={client} store={store}>
            <RouterContext {...renderProps} />
          </ApolloProvider>
        )

        /**
         * Render and send response
         */
        renderToStringWithData(App).then(content => {
          const html = <Html content={content}/>

          res.status(200).send(`<!DOCTYPE html>\n${ renderToString(html) }`)
        }).catch((err) => console.log(`INITIAL RENDER (SSR) ERROR:`, err))
      } else {
        res.status(404).send('Not found')
      }
    }
  )
}

Upvotes: 1

Related Questions