Redux server side rendering breaks when using an apollo graphql client - react-redux

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);

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')
}
}
)
}

Related

Can't use React useEffect and also build failed using Gatsby

I am building a headless eCommerce website using Nacelle, Gatsby, and Shopify plus.
My problem is that I integrated Okendo API to fetch product reviews and can't build the project.
Actually, as you know, headless eCommerce is a new technology to us, but it is mostly close to Gatsby and SSR.
I tried to go 2 ways, one is to include the script to head using gatsby-react-helmet, and another one is to call window api inside useEffect or useLayoutEffect.
1. Including the script to head tag using gatsby-plugin-react-helmet.
ProductReview.js
import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet';
import transformProductId from '../../utils/transformProductId';
import { PRODUCT_REVIEW_METAFIELD_KEY, OKENDO_SUBSCRIBER_ID } from '../../constants';
const ProductReview = ({
product
}) => {
const OkendoSettings = {
filtersEnabled: true,
omitMicrodata: true,
subscriberId: OKENDO_SUBSCRIBER_ID,
widgetTemplateId: "default"
}
return (
<>
<Helmet>
<script type="application/javascript" src="../plugins/okendo/index.js" />
<script type="application/json" id="oke-reviews-settings">
{JSON.stringify(OkendoSettings)}
</script>
<script type="application/javascript" src="../plugins/okendo/initAPI.js" />
</Helmet>
<div
data-oke-reviews-widget
data-oke-reviews-product-id={transformProductId(product.id)}
/>
</>
);
};
export default React.memo(ProductReview);
/plugin/okendo/index.js
(function () {
function asyncLoad() {
var urls = ['https:\/\/d3hw6dc1ow8pp2.cloudfront.net\/reviewsWidget.min.js?shop=example.myshopify.com'];
for (var i = 0; i < urls.length; i++) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = urls[i];
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}
}
if (window.attachEvent) {
window.attachEvent('onload', asyncLoad);
} else {
window.addEventListener('load', asyncLoad, false);
}
})();
/plugin/okendo/initAPI.js
window.okeReviewsWidgetOnInit = function (okeInitApi) {};
If I include the Okendo scripts to head tag, it works all fine.
But when I try to build on vercel, it says "error Building static HTML failed for path /products/example-product-slug".
2. Calling window.init api inside useEffect.
ProductReview.js
import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet';
import transformProductId from '../../utils/transformProductId';
import { PRODUCT_REVIEW_METAFIELD_KEY, OKENDO_SUBSCRIBER_ID } from '../../constants';
const ProductReview = ({
product
}) => {
const OkendoSettings = {
filtersEnabled: true,
omitMicrodata: true,
subscriberId: OKENDO_SUBSCRIBER_ID,
widgetTemplateId: "default"
}
useEffect(() => {
if (typeof window !== `undefined` && window.okendoInitApi) {
const reviewsWidget = window.document.querySelector('#oke-reviews-widget');
window.okendoInitApi.initReviewsWidget(reviewsWidget);
}
}, [product.id]);
return (
<>
<Helmet>
<script type="application/javascript" src="../plugins/okendo/index.js" />
<script type="application/json" id="oke-reviews-settings">
{JSON.stringify(OkendoSettings)}
</script>
{/* <script type="application/javascript" src="../plugins/okendo/initAPI.js" /> */}
</Helmet>
<div
id="oke-reviews-widget"
data-oke-reviews-widget
data-oke-reviews-product-id={transformProductId(product.id)}
/>
</>
);
};
export default React.memo(ProductReview);
While I am using useEffect to initialize Okendo api, it works only when the page refresh, not work if I open a page.
And if I try to build it, it says "error "window" is not available during server side rendering.".
I know useEffect doesn’t run unless it’s in the browser, but still I don't get what the solution is.
Hope to hear a good news.
Thank you.
UPDATE: The product id is generated from Shopify product graphql data named handle.
gatsby-node.js
exports.createPages = async ({ graphql, actions: { createPage } }) => {
// Fetch all products
const products = await graphql(`
{
allNacelleProduct (filter: { availableForSale: {eq: true} }) {
edges {
node {
handle
}
}
}
}
`);
products.data.allNacelleProduct.edges.forEach((product) =>
createPage({
// Build a Product Detail Page (PDP) for each product
path: `/products/${product.node.handle}`,
component: path.resolve('./src/templates/product-detail.js'),
context: {
handle: product.node.handle
}
})
);
...

[updated]Intergrating NextJS and Redux State Management

this my updated version of intergrating redux and NextJS. Just to elobarate what I have done so far...
STEP 1. I've created a store.js file to set up my global store in reference to github's explanation from nextJS developers.
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import thunkMiddleware from 'redux-thunk';
import { customerListReducer } from './customerReducers';
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const combinedReducer = combineReducers({
customerList: customerListReducer,
});
const reducer = (state, action) => {
console.log('Just Displaying the Store', state);
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.count) nextState.count = state.count; // preserve count value on client side navigation
return nextState;
} else {
return combinedReducer(state, action);
}
};
// create a makeStore function
const store = () =>
createStore(
reducer,
bindMiddleware([thunkMiddleware])
);
// export an assembled wrapper
export const wrapper = createWrapper(store);
STEP 2: Imported the wrapper above in my _app file to make the wrapper available across all pages in my application
import Nav from '../components/Nav';
import {wrapper} from '../reducers/store';
function MyApp({ Component, pageProps }) {
return (
<>
<Nav />
<Component {...pageProps} />
</>
);
}
export default wrapper.withRedux(MyApp);
STEP 3: CONFIGURATIONS
A) My Action that calls external API
import axios from 'axios';
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
export const listCustomers = () => async (dispatch) => {
try {
dispatch({
type: CUSTOMER_LIST_REQUEST,
});
const { data } = await axios.get(
'https://byronochara.tech/gassystem/api/v1/customers'
);
const result = data.results;
dispatch({
type: CUSTOMER_LIST_SUCCESS,
payload: result,
});
} catch (error) {
dispatch({
type: CUSTOMER_LIST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
B)My Action Reducer
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
import { HYDRATE } from 'next-redux-wrapper';
export const customerListReducer = (state = { customers: [] }, action) => {
switch (action.type) {
case HYDRATE:
return { loading: true, customers: [] };
case CUSTOMER_LIST_REQUEST:
return { loading: true, customers: [] };
case CUSTOMER_LIST_SUCCESS:
return {
loading: false,
customers: action.payload,
};
case CUSTOMER_LIST_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
C)The finally bringing it all together in my index.js page to display the results:
import React, { useEffect } from 'react';
import Head from 'next/head';
import { useSelector} from 'react-redux';
import { listCustomers } from './../actions/customerActions';
import { wrapper } from '../reducers/store';
import styles from '../styles/Home.module.css';
const Home = () => {
//Select the loaded customers' list from central state
const customerList = useSelector((state) => {
console.log(state);
return state.customerList;
});
const { loading, error, customers } = customerList;
//displaying the customers data from the external API
console.log('Fetched Customers Data', customers);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{/* {loading && <h6>Loading...</h6>} */}
{/* {error && <h6>Error Occured...</h6>} */}
{/* {customers.map((customer) => (
<h3>{customer.customerName}</h3>
))} */}
{/* <ArticleList customers={customers} /> */}
</div>
);
};
// getStaticProp at build time
// getServerSideProp at every request slower
// getStaticPath to dynamically generate paths based on the data we are fetching
export const getStaticProps = wrapper.getServerSideProps(async ({ store }) => {
// console.log('STORE', store);
store.dispatch(listCustomers());
});
export default Home;
COMMENT ON THE PROBLEM I'M FACING FROM THE ABOVE CODE: once everything has been set up if you follow the code above, the code seems to run well the store is successfully created when I log the result on the console ``{ customerList: { loading: true, customers: [] } }. But then I guess this is the result from the HYDRATE action type since it will always be dispatch since am using getStaticProps``` that creates a new store instance in the server.
MAIN QUIZ: My challenge is how do I bypass the HYDRATED action and reconcile the server side state with the client side store and persist it and at least to finally be able to view the list from the external API. Thanks in advance. :)
I totally recommend you to use reduxjs/toolkit. It's very simple , less code, no wrappers, clean. And no matter your project on nextjs or created via CRA. Also you dont need to configure redux-thunk and redux-devtools cause they are enabled by default. Read documentation for more information ( how to persist state without any npm package and so on )
Here is a little example.
store.js
import { combineReducers, configureStore } from "#reduxjs/toolkit";
import userSlice from './user.slice.js';
//reducers
const rootReducer = combineReducers({
user: userSlice
});
const store = configureStore({
reducer: rootReducer,
});
export default store;
Wrap with Provider (in your case _app.js)
<Provider store={store}>
<Component {...pageProps} />
</Provider>
user.slice.js ( action + reducer )
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
id: '',
email: '',
roles: []
};
// export async action
export const signIn = createAsyncThunk('user/signIn', async (data) => {
try {
const payload = await api.auth.signin(data).then((res) => res.data);
// do some stuff if you want
return payload ;
} catch (err) {
console.log(err.response);
}
});
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
removeUser(state, payload) {
//cant be an async method
return initialState;
},
extraReducers: (builder) => {
builder.addCase(signIn.fulfilled, (state, { payload }) => {
// payload from the async method above (asyncThunk)
return payload;
});
},
},
});
// export actions
export const { removeUser } = userSlice.actions;
// export reducer
export default userSlice.reducer;
Thats it. Last step to call actions from any component e.g.
import { useDispatch, useSelector } from 'react-redux';
import { signIn, removeUser } from '../actions/userSlice';
// in function component
// call hooks
const dispatch = useDispatch();
// read the store
const { user } = useSelector((state) => state);
// dispatch any action , example below
dispatch(signIn(userCredentials));
// or
dispatch(removeUser());
I has an Issue with setting Redux with NextJS and this is my final answer after some insight from mirik999 too.
A. my store.
import { configureStore } from '#reduxjs/toolkit';
//importing the slice file with sliced reducers
import customerListReducer from '../slices/customerSlice';
// const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware));
const store = configureStore({
reducer: {
customerList: customerListReducer,
},
});
export default store;
B. The store is provided in my app component
function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<Nav />
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
C. The Slice file that automatically creates action creators and the reducer
import { createSlice } from '#reduxjs/toolkit';
//creating and action that calls API from a REST API backend
export const customersFetchedList = createAsyncThunk(
'customersList/customersListSuccess',
async () => {
try {
const { data } = await axios.get(
'https://example.com/api/your/endpoint'
);
const result = data.results;
//the payload
const payload = result;
return payload;
} catch (error) {
console.log(error.response);
const payload =
error.response && error.response.data.message
? error.response.data.message
: error.message;
return payload;
}
}
);
const initialState = {
loading: true,
customers: [],
error: false,
};
const customerListSlice = createSlice({
name: 'customersList',
initialState,
reducers: {
//reducer functions we've provided
customersRequest(state, action) {
if (state.loading == true) {
return state;
}
},
},
extraReducers: (builder) => {
initialState,
builder.addCase(customersFetchedList.fulfilled, (state, action) => {
state.loading = false;
state.customers = action.payload;
state.error = false;
return state;
});
},
});
export const {
customersRequest,
customersLoadingError,
} = customerListSlice.actions;
export default customerListSlice.reducer;
D. Then finally fired this action above in my component using the useEffect()
import React, { useEffect } from 'react';
import Head from 'next/head';
const Home = () => {
//method to fire the action
const dispatch = useDispatch();
//Select the loaded customers' list from central state
const customerList = useSelector((state) => state);
// const { loading, error, customers } = customerList;
useEffect(() => {
dispatch(listCustomers());
}, []);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{loading && <h6>Loading...</h6>}
{error && <h6>Error Occured...</h6>}
{customers.map((customer) => (
<h3>{customer.customerName}</h3>
))}
</div>
);
};
Thanks so much for your contribution. :)

Rendering JSON with GraphQL in GatsbyJS

I've been stuck for hours and I have no idea what's wrong. I'm new to GraphQL and Gatsby.
I have a JSON file that I'm trying to render. Each object has its own url path. I'm able to query the data with GraphQL, set up CreatePages in my gatsby-node.js file and then query again in my template file, but for some reason I'm only getting null in the template file.
I'm pretty sure I'm using gatsby-plugin-transformer-json correctly too. I'm at a loss right now. Any help is much appreciated.
gatsby-node.js
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
// You can delete this file if you're not using it
const path = require('path');
exports.createPages = ({actions, graphql}) => {
const { createPage } = actions;
const postTemplate = path.resolve(`src/templates/post.js`);
const projectTemplate = path.resolve(`src/templates/project.js`);
return graphql(`{
allProjectsJson {
edges {
node {
id
name
description
path
}
}
}
allMarkdownRemark {
edges {
node {
html
id
frontmatter {
path
title
date
}
}
}
}
}`)
.then(res => {
if(res.errors) {
return Promise.reject(res.errors);
}
// blogs
res.data.allMarkdownRemark.edges.forEach(({node}) => {
createPage({
path: node.frontmatter.path,
component: postTemplate
})
})
// projects
res.data.allProjectsJson.edges.forEach(({node}) => {
createPage({
path: node.path,
component: projectTemplate
})
})
})
}
templates/project.js
import React from 'react';
import { graphql } from 'gatsby'
import Layout from "../components/layout"
// import Helmet from 'react-helmet';
export default function Project({data}) {
const { projectsJson: project } = data;
// this results in null???
console.log(data)
return (
<Layout>
<div>
<h1>Projects!!!</h1>
<p>One single</p>
</div>
</Layout>
);
}
export const projectQuery = graphql`
query ProjectByPath($path: String!) {
projectsJson(path: { eq: $path }) {
name
path
description
}
}
`
I decided to simply list the projects rather than be able to link to each one. With that, I simply had to query in the file rather than pass each object through gatsby-node.js and grab the right one in the template file.
import React from "react"
// import { Link } from "gatsby"
import { graphql } from 'gatsby'
import Layout from "../components/layout"
import SEO from "../components/seo"
const Projects = ({data}) => (
<Layout>
<SEO title="Projects" />
<h1>Personal Projects</h1>
{data.allProjectsJson.edges.map((project, index) => {
console.log(project)
return (
<a
key={index}
style={projectContainerStyles}
href={project.node.url}
target="_blank"
rel="noopener noreferrer"
>
<h2>{project.node.name}</h2>
{project.node.description}
</a>
)
})}
<p>You can see my external portfolio
<a
href="https://anthonyzamarro.github.io/az_portfolio/"
target="_blank"
rel="noopener noreferrer">
here!
</a>
</p>
</Layout>
)
const projectContainerStyles = {
marginBottom: '2rem',
background: 'turquoise',
padding: '8px',
borderRadius: '5px',
boxShadow: '1px 3px 2px rgb(155,155,155)',
'display': 'block',
'color': '#000',
textDecoration: 'none'
}
export const projectQuery = graphql`
query projectQuery {
allProjectsJson {
edges {
node {
id
name
description
url
}
}
}
}`
export default Projects

How to use QueryComponent with pollInterval and local state changes

i'm writing a small search app which starts a search over a RESTapi. Until the search is finished the api respond with a 260 status code or a 250 if the search is finished.
For this purpose i've written a component with a Query component which polls the RESTapi:
import React from 'react';
import gql from "graphql-tag";
import { Query } from "react-apollo";
export const GET_SEARCH_RESULTS = gql`
query GetSearchResults($sid: String!) {
getSearchResults(sid: $sid) {
status,
clusteredOffers {
id,
bookingId,
totalPrice
}
}
}
`;
export default class extends React.PureComponent {
client = null;
updateStatus = (data) => {
this.client.writeData({ data: { currentSearch: { status:
data.getSearchResults.status, __typename: 'CurrentSearch' }} })
};
render() {
const { sid } = this.props;
return (
<Query
query={GET_SEARCH_RESULTS}
variables={{ sid }}
pollInterval={500}
onCompleted={this.updateStatus}
>
{({ loading, error, data, stopPolling, client }) => {
this.client = client;
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
if(data.getSearchResults.status &&
data.getSearchResults.status !== "260") stopPolling();
return (
<div>
{data.getSearchResults.clusteredOffers.map(offer => (
<div key={offer.id} >
<div>{offer.id}</div>
<div>{offer.bookingId}</div>
<div>{offer.totalPrice}</div>
</div>
))}
</div>
)
}}
</Query>
)
}
}
In a parent component i also need the status of the current search. So i've added a clientState to my ApolloClient to write the remote data to the client cache:
import ApolloClient from "apollo-boost";
import Search from './components/Search';
const client = new ApolloClient({
uri: "http://localhost:4000/graphql",
clientState: {
defaults: {
currentSearch: {
__typename: 'CurrentSearch',
status: "0"
}
},
}
});
....
const GET_CURRENT_SEARCH_STATE = gql`
{
currentSearch #client {
status
}
}
`;
As u can see i'm using onCompleted inside my polling query component to write the current status to the local cache.
Does it make sense or should i read the status from the cached remote data? If so, how i get the cached remote data?
thank you!

Getting checksum invalid warning when using apollo client isomorphically with react starter kit

I'm trying to use the Apollo client and I am running into a few issues with dropping it into the react starter kit with redux. https://github.com/kriasoft/react-starter-kit/tree/feature/redux
Trying to use the techniques from here: http://dev.apollodata.com/react/server-side-rendering.html
But I get the error
warning.js:36Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) </div></header><div data-reactid="19">Lo
(server) </div></header><div class="Home-root-2IM
Here's my implementation
// server.js
...
const component = (
<App context={context}>
<ApolloProvider client={context.client} store={context.store}>
{route.component}
</ApolloProvider>
</App>
);
await getDataFromTree(component);
data.children = ReactDOM.renderToString(component);
data.style = [...css].join('');
data.scripts = [
assets.vendor.js,
assets.client.js,
];
data.state = context.store.getState();
if (assets[route.chunk]) {
data.scripts.push(assets[route.chunk].js);
}
const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);
res.status(route.status || 200);
res.send(`<!doctype html>${html}`);
...
And client side
// client.js
...
const component = (
<App context={context}>
<ApolloProvider client={context.client} store={context.store}>
{route.component}
</ApolloProvider>
</App>
);
appInstance = ReactDOM.render(
component,
container,
() => onRenderComplete(route, location),
);
...
// Home.js
class Home extends React.Component {
static propTypes = {
collections: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
subtitle: PropTypes.string.isRequired,
photo: PropTypes.string,
})).isRequired,
};
render() {
const props = this.props;
const { loading, allCollections } = props.data;
if (loading) {
return <div>Loading</div>;
} else {
return (
<div className={s.root}>
<div className={s.container}>
<h1 className={s.title}>Collections</h1>
<ul>
{allCollections.map((collection) =>
<li key={collection.id}>
<h3>{collection.title}</h3>
<img src={collection.photo} width="200"/>
</li>
)}
</ul>
</div>
</div>
);
}
}
}
Home.propTypes = {
data: PropTypes.shape({
loading: PropTypes.bool.isRequired,
allCollections: PropTypes.array,
}).isRequired,
};
const HomeWithStyles = withStyles(s)(Home);
const HomeWithData = graphql(getQuery)(HomeWithStyles);
export default connect()(HomeWithData);
// App.js
import React, { Children, PropTypes } from 'react';
const ContextType = {
// Enables critical path CSS rendering
// https://github.com/kriasoft/isomorphic-style-loader
insertCss: PropTypes.func.isRequired,
// Integrate Redux
// http://redux.js.org/docs/basics/UsageWithReact.html
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired,
}).isRequired,
client: PropTypes.object.isRequired,
};
/**
* The top-level React component setting context (global) variables
* that can be accessed from all the child components.
*
* https://facebook.github.io/react/docs/context.html
*
* Usage example:
*
* const context = {
* history: createBrowserHistory(),
* store: createStore(),
* };
*
* ReactDOM.render(
* <App context={context}>
* <Layout>
* <LandingPage />
* </Layout>
* </App>,
* container,
* );
*/
class App extends React.PureComponent {
static propTypes = {
context: PropTypes.shape(ContextType).isRequired,
children: PropTypes.element.isRequired,
};
static childContextTypes = ContextType;
getChildContext() {
return this.props.context;
}
render() {
// NOTE: If you need to add or modify header, footer etc. of the app,
// please do that inside the Layout component.
return Children.only(this.props.children);
}
}
export default App;
So the answer was that the initial state was not set for the apollo client data
// client.js
let apolloOptions = {
ssrMode: false,
initialState: { apollo: { data: window.APP_STATE.apollo.data } }, // NOT __APOLLO_STATE__ as set in some of the examples online
ssrForceFetchDelay: 500,
networkInterface: createNetworkInterface({
uri: window.APP_STATE.runtime.graphUri,
}),
opts: {
credentials: 'same-origin',
},
};
const client = new ApolloClient(apolloOptions);
And on the server
// server.js
...
// data.state gets rendered by the server to client as APP_STATE
data.state = context.store.getState();
data.state.apollo = client.store ? client.store.getState().apollo : null;
...

Resources