apollographql / react-apollo

:recycle: React integration for Apollo Client
https://www.apollographql.com/docs/react/
MIT License
6.85k stars 787 forks source link

How to avoid client re fetching in react-apollo SSR with redux? #797

Closed collegepinger closed 7 years ago

collegepinger commented 7 years ago

I would like to use react apollo with redux also server side rendering Every thing is fine my app is working but the problem is when my app render's it is actually recalling the api again it is not using my rendered state .What is a proper way of doing it.

server.js

import express from 'express';
import bodyParser from 'body-parser';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import { StaticRouter } from 'react-router';
import { ApolloClient, createNetworkInterface, ApolloProvider } from 'react-apollo';
import { getDataFromTree } from "react-apollo"

import path from 'path';
import expressGraphQL from 'express-graphql';
import schema from './GraphQL/Schema';
import App from '../client/App'
import HMR from './serverUtils/HMR';
import store from '../client/Redux/Store';

require('es6-promise').polyfill();
require('isomorphic-fetch');

const app =express();
app.use(bodyParser.json());
app.use('/api', expressGraphQL({
    schema,
    graphiql: true
}));
app.use('/static',express.static('build'));

// HMR(app);

// app.get('*',(req,res) => {
//
//     res.sendFile(path.join(__dirname,'./onlyDev.html'));
//
// });

app.get('*',(req,res) => {
    const routeContext = {};

    const client = new ApolloClient({
        ssrMode: true,
        // Remember that this is the interface the SSR server will use to connect to the
        // API server, so we need to ensure it isn't firewalled, etc
        networkInterface: createNetworkInterface({
            uri: 'http://localhost:3000/api',
            // opts: {
            //     credentials: 'same-origin',
            //     headers: {
            //         cookie: req.header('Cookie'),
            //     },
            // },
        }),
    });

    const components = (
        <StaticRouter location={req.url} context={routeContext}>
            <ApolloProvider client={client} store={store}>
                <App />
            </ApolloProvider>
        </StaticRouter>
    );

    getDataFromTree(components).then(() => {
        const content = ReactDOMServer.renderToString(components);
        const initialState = {apollo: {
          data: client.store.getState().apollo.data
        }};
        console.log(initialState,'I am initial Stare')

        const html = <Html content={content} state={initialState} />;
        res.status(200);
        res.send(`<!doctype html>\n${ReactDOMServer.renderToStaticMarkup(html)}`);
        res.end();

    })

});

function Html({ content, state }) {
    return (
        <html>
        <body>
        <div id="app" dangerouslySetInnerHTML={{ __html: content }} />

        <script src="/static/vendor.js" />
        <script src="/static/app.js" />
        <script dangerouslySetInnerHTML={{
            __html: `window.__APOLLO_STATE__=${JSON.stringify(state).replace(/</g, '\\u003c')};`,
        }} />
        </body>
        </html>
    );
}

app.listen(3000,() => {
    console.log('I am Listening')
})

Apollo/index.js

import ApolloClient, {
    createNetworkInterface,

} from 'apollo-client';

const client =  new ApolloClient({
    networkInterface: createNetworkInterface({
        uri: 'http://localhost:3000/api',
    }),
    reduxRootSelector: state => state.apollo,
    //Because of window while server side rendering
    initialState: (typeof window !== 'undefined'?window.__APOLLO_STATE__:{}),
});

export default client

client.js

import {render} from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { BrowserRouter } from 'react-router-dom';
import { ApolloProvider } from 'react-apollo';
import client from './Apollo';
import store from './Redux/Store';

import App from './App'

const renderApp = (Component) => {
    render(
        <AppContainer>

            <ApolloProvider store={store}  client={client} >
                <BrowserRouter>
                    <Component />
                </BrowserRouter>
            </ApolloProvider>

        </AppContainer>,
        document.getElementById('app')
    );
};

if(typeof document !== 'undefined'){
    renderApp(App);
}

if (module.hot) {
    module.hot.accept('./App', () => {
        const nextApp = require('./App');
        renderApp(nextApp);
    });
}

export default  renderApp(App);

store.js

import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import {createLogger} from 'redux-logger';
import client from '../../Apollo'
import rootReducer from '../Reducers'

import {initialState as CurrentUser} from '../Reducers/CurrentUser';
const isProduction = process.env.NODE_ENV !== 'development';
const isClient = typeof document !== 'undefined';
const initialState = {
    CurrentUser
};

const middlewares = [thunk, client.middleware()];
const enhancers = [];

if (!isProduction && isClient) {
    const loggerMiddleware = createLogger();
    middlewares.push(loggerMiddleware);

    if (typeof devToolsExtension === 'function') {
        const devToolsExtension = window.devToolsExtension;
        enhancers.push(devToolsExtension());
    }
}
const composedEnhancers = compose(
    applyMiddleware(...middlewares),
    ...enhancers
);

const store = createStore(
    rootReducer,
    initialState,
    composedEnhancers,
);

export default store;

container/Home.js

import React,{Component} from 'react';
import gql from 'graphql-tag';
import { connect } from 'react-redux';
import {graphql} from 'react-apollo';

class Home extends Component{
    renderPosts(){
        console.log(this.props)
        const {loading,posts} = this.props;

        console.log(this.props)
        if(!loading){
            return posts.map(({id,title,body}) => {
                return (
                    <li key={id}>{title}</li>
                )
            })

        }else{

            return <li>Loading...</li>

        }
    }
    render(){
        return(
            <div>
                {this.renderPosts()}
            </div>
        )
    }
}

const mapStateToPros = (state) => ({
    CurrentUser:state.CurrentUser
});

const AllPosts = gql`
query getAllPosts{
  posts{
    id
    title
    body
  }
}
`;

const HomeWithData = graphql(AllPosts,{
    props:({ data:{loading,posts} }) =>({
        posts,
        loading
    })
})(Home)

export default connect(mapStateToPros)(HomeWithData)
stale[bot] commented 7 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if not further activity occurs. Thank you for your contributions to React Apollo!

jcheroske commented 7 years ago

Here is our apollo/redux initialization code. Sorry I don't have more time right now to walk you through it. It's an HOC for use with next.js. It puts the redux state from the server into a prop called initialState that is transferred from the server to the client.

import * as sharedReducers from 'ducks'
import * as sharedEpics from 'ducks/epics'
import * as sharedLogic from 'ducks/logic'
import fetch from 'isomorphic-fetch'
import {get, flatten, set, values} from 'lodash/fp'
import {defaultGetInitialProps} from 'next-extras'
import PropTypes from 'prop-types'
import {Component} from 'react'
import {ApolloClient, ApolloProvider, createNetworkInterface, getDataFromTree, IntrospectionFragmentMatcher} from 'react-apollo'
import {combineForms} from 'react-redux-form'
import {createEagerFactory} from 'recompose'
import {createStore, applyMiddleware, compose, combineReducers} from 'redux'
import DevTools from 'redux-devtools-extras'
import {createLogicMiddleware} from 'redux-logic'
import {combineEpics, createEpicMiddleware} from 'redux-observable'
import thunkMiddleware from 'redux-thunk'
import graphqlSchema from '../../graphql.schema.json'

// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
  global.fetch = fetch
}

export default ({appReducers, appEpics, appLogic, appForms}) => {
  let apolloClientCache
  const createApolloClient = () => {
    if (process.env.NODE_ENV !== 'production' && apolloClientCache != null) {
      return apolloClientCache
    }

    const fragmentMatcher = new IntrospectionFragmentMatcher({
      introspectionQueryResultData: graphqlSchema
    })

    console.log('gql endpoint', process.env.GRAPH_QL_ENDPOINT)
    const networkInterface = createNetworkInterface({ uri: process.env.GRAPH_QL_ENDPOINT })
    networkInterface.use([{
      applyMiddleware (req, next) {
        let headers = get('options.headers')(req)
        if (headers == null) {
          headers = {}
          set('options.headers', headers)(req)
        }

        // get the authentication token from local storage if it exists
        if (typeof localStorage !== 'undefined' && localStorage.getItem('auth0IdToken')) {
          headers.authorization = `Bearer ${localStorage.getItem('auth0IdToken')}`
        }
        next()
      }
    }])

    const apolloClient = new ApolloClient({
      ssrMode: !process.browser,
      fragmentMatcher,
      networkInterface
    })

    if (process.browser) {
      apolloClientCache = apolloClient
    }

    return apolloClient
  }

  let reduxStoreCache
  const createReduxStore = ({apolloClient, initialState}) => {
    if (reduxStoreCache != null) {
      return reduxStoreCache
    }

    const rootEpic = combineEpics(
      ...Object.values(sharedEpics).reduce((memo, duckEpics) => [...memo, ...Object.values(duckEpics)], []),
      ...Object.values(appEpics).reduce((memo, duckEpics) => [...memo, ...Object.values(duckEpics)], [])
    )

    const rootLogic = [
      ...flatten(values(sharedLogic).map(values)),
      ...flatten(values(appLogic).map(values))
    ]

    const rootFormConfig = Object.values(appForms).reduce((memo, duckForms) => Object.assign(memo, duckForms), {})

    const rootReducer = combineReducers({
      apollo: apolloClient.reducer(),
      forms: combineForms(rootFormConfig, 'forms'),
      ...sharedReducers,
      ...appReducers
    })

    const storeEnhancers = compose(
      applyMiddleware(
        apolloClient.middleware(),
        thunkMiddleware,
        createEpicMiddleware(rootEpic),
        createLogicMiddleware(rootLogic)
      ),
      DevTools.instrument()
    )

    const reduxStore = createStore(rootReducer, initialState, storeEnhancers)

    if (process.browser) {
      reduxStoreCache = reduxStore
    }

    return reduxStore
  }

  return BaseComponent => {
    const factory = createEagerFactory(BaseComponent)

    class ApolloReduxProvider extends Component {
      static propTypes = {
        initialState: PropTypes.object
      }

      static async getInitialProps (pageContext) {
        const apolloClient = createApolloClient()
        const reduxStore = createReduxStore({apolloClient})

        pageContext = {
          ...pageContext,
          reduxStore
        }

        const composedProps = await defaultGetInitialProps(BaseComponent)(pageContext)

        let newProps = {}
        if (!process.browser) {
          const app = (
            <ApolloProvider client={apolloClient} store={reduxStore}>
              {factory(composedProps)}
            </ApolloProvider>
          )
          await getDataFromTree(app)

          newProps = {
            initialState: {
              ...reduxStore.getState(),
              apollo: {
                data: apolloClient.getInitialState().data
              }
            }
          }
        }

        return {
          ...composedProps,
          ...newProps
        }
      }

      constructor (props) {
        super()
        this.client = createApolloClient()
        this.store = createReduxStore({
          apolloClient: this.client,
          initialState: props.initialState
        })
      }

      render () {
        const {initialState: _, ...rest} = this.props
        return (
          <ApolloProvider client={this.client} store={this.store}>
            {factory(rest)}
          </ApolloProvider>
        )
      }
    }

    return ApolloReduxProvider
  }
}
stale[bot] commented 7 years ago

This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!

stale[bot] commented 7 years ago

This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!

stale[bot] commented 7 years ago

This issue has been automatically closed because it has not had recent activity after being marked as no recent activyt. If you belive this issue is still a problem or should be reopened, please reopen it! Thank you for your contributions to React Apollo!