Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am new to graphql with react-appollo 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 ..

enter image description here

server .js

import express from 'express';
import bodyParser from 'body-parser';

import path from 'path';
import expressGraphQL from 'express-graphql';
import schema from './GraphQL/Schema';
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 store from '../client/Redux/Store/store';

import {serverClient} from './lib/apollo'

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

import WApp from '../client/App';

//Dev HMR
import HMR from './serverUtils/HMR';

const app = express();
app.use(bodyParser.json());

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

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

function createReactHandler(req) {
    return async function reactHandler(ctx) {
        const routeContext = {};
        const client = serverClient();

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

        await getDataFromTree(components);

        // const html = ReactDOMServer.renderToString(components);

        // // Handle redirects
        // if ([301, 302].includes(routeContext.status)) {
        //     // 301 = permanent redirect, 302 = temporary
        //     ctx.status = routeContext.status;
        //
        //     // Issue the new `Location:` header
        //     ctx.redirect(routeContext.url);
        //
        //     // Return early -- no need to set a response body
        //     return;
        // }
        //
        // // Handle 404 Not Found
        // if (routeContext.status === 404) {
        //     // By default, just set the status code to 404.  You can add your
        //     // own custom logic here, if you want to redirect to a permanent
        //     // 404 route or set a different response on `ctx.body`
        //     ctx.status = routeContext.status;
        // }

        //   return html;
        // console.log(html)


    }
}



const HTML = ({ html,state}) => (

    <html lang="en" prefix="og: http://ogp.me/ns#">
    <head>
        <meta charSet="utf-8" />
        <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
        <meta httpEquiv="Content-Language" content="en" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />

    </head>
    <body>
    <div
        id="app"
        dangerouslySetInnerHTML={{ __html: html }} />
    <script dangerouslySetInnerHTML={{
        __html: `window.__STATE__=${JSON.stringify(state)};`,
    }} />

    <script src="/static/app.js" />

    </body>
    </html>
);

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

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

    getDataFromTree(components).then(() => {
        const html = ReactDOMServer.renderToString(components);
        const initialState = {apollo: client.getInitialState()}

        console.log(client);

        res.send(`<!DOCTYPE html>\n${ReactDOMServer.renderToStaticMarkup(
            <HTML
                html={html}
                state={initialState}
                 />,
        )}`)
    })
})




app.listen(3000,() => {
    console.log('Man I on')
})

store.js

import { createStore, compose, applyMiddleware } from 'redux';
import { syncHistoryWithStore } from 'react-router-redux';
import thunk from 'redux-thunk';
import {createLogger} from 'redux-logger';


import client from '../apolloClient';
import rootReducer from '../Reducers'

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

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,
    {},

    composedEnhancers,
);

export default store;

apolloClient.js

import ApolloClient, {
    createNetworkInterface,

} from 'apollo-client';
const isProduction = process.env.NODE_ENV !== 'development';
const testUrl = 'http://localhost:3000/api';

// const url = isProduction ? productionUrl : testUrl;
const url =  testUrl;


const client = new ApolloClient({

    networkInterface: createNetworkInterface({uri:testUrl}),
    dataIdFromObject:({id}) => id,
    initialState: (typeof window !=='undefined')? window.__STATE__:{},
    reduxRootSelector:state => state.custom

});

export default client;

Home.js

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

import gql from 'graphql-tag';

import * as postActions from '../../Redux/Actions/postActions';


class Home extends Component{
    componentWillMount(){
        // console.log('From Will Mount',this.props.posts)
    }
    renderAllPost(){
        const {loading,posts} = this.props;

        if(!loading){
            return posts.map(data => {
                return <li key={data.id}>{data.title}</li>
            })
        }else{
            return <div>loading</div>
        }
    }
    render(){

        return(
            <div>

                {this.renderAllPost()}

            </div>
        )
    }
}


//start from here
const GetallPosts = gql`
query getAllPosts{
  posts{
    id
    title
    body
  }
}
`;

const mapDispatchToProps = (dispatch) => ({
    actions:bindActionCreators(
        postActions,
        dispatch
    )
});


const ContainerWithData = graphql(GetallPosts,{
    props:({ data:{loading,posts} }) => ({
        posts,
        loading,
    })
})(Home)


export default connect(
    // mapStateToPros,
    // mapDispatchToProps
)(ContainerWithData)
like image 953
Nane Avatar asked Nov 07 '22 20:11

Nane


1 Answers

Can I confirm that I understand the problem correctly?

You are rendering the HTML server side.

  • The HTML (Including all the posts) are in the HTML returned to the browser.
  • React then changes this to the loading window
  • React then makes the API call, and renders the new posts

NOTE: Apollo will always make the AJAX call as this is done automatically as part of the ContainerWithData.

Solution Render the Redux Store with all the data. For example, when making the call to "createStore", you are currently passing in an empty object. If you make an AJAX call here, you can populate the browser/redux store with all the data required.

At this point, you can remove the call to GraphQL within the Container. You would replace this with some logic with "componentWillMount".

The logic would be:

  • Create store using model data returned from API
  • Call Home Component
  • Home runs "componentWillMount"
  • componentWillMount checks if store.posts has data
    • then load data from API (GraphQL)
    • else return true and continue rendering
like image 95
Garry Taylor Avatar answered Nov 14 '22 22:11

Garry Taylor