Search code examples
ajaxreactjsreduxserver-side-renderingisomorphic-javascript

react + redux + saga + server side rendering + how to stop additional ajax calls for server side rendered page?


I am working on a web project. In this project we use server side rendering with node , express and react. For fetching data and Redux , we use Redux-Saga. There is only one problem. Every page is rendered from server and when we get to browser, our app acts like a client side application. If a page has some ajax calls, and that page renders from server, although it has all data it needs from server , in client side it makes the ajax calls. I want to stop the additional ajax calls. I want to skip the ajax calls for the requested page from server only.

this is index.jsx file for client side.

import App from './App'
import ReactDOM from 'react-dom'
import React from 'react';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux'
import getStore from './getStore';
import createHistory from 'history/createBrowserHistory';

import './styles/styles.scss';

const history = createHistory();
const store = getStore(history);

if (module.hot) {
    module.hot.accept('./App', () => {
        const NextApp = require('./App').default;
        render(NextApp);
    });
}

const render = (_App) => {
    ReactDOM.render(
    <Provider store={store}>
        <ConnectedRouter history={history}>
            <_App />
        </ConnectedRouter>
    </Provider>
    , document.getElementById("AppContainer"));
};

store.subscribe(() => {
const state = store.getState();
if (state.items.length > 0) {
    render(App);
}
});
const fetchDataForLocation = location => {
if (location.pathname === "/") {
    store.dispatch({ type: `REQUEST_FETCH_ITEMS` });
}
};
fetchDataForLocation(history.location);
 history.listen(fetchDataForLocation);

this is the index.js file for server

import path from 'path';
import express from 'express';
import webpack from 'webpack';
import yields from 'express-yields';
import fs from 'fs-extra';
import App from '../src/App';
import { renderToString } from 'react-dom/server';
import React from 'react'
import { argv } from 'optimist';
import { ConnectedRouter } from 'react-router-redux';
import getStore from '../src/getStore'
import { Provider } from 'react-redux';
import createHistory from 'history/createMemoryHistory';
import open from 'open';
import { get } from 'request-promise';

const port = process.env.PORT || 4000;
const app = express();

const useServerRender = argv.useServerRender === 'true';
const inDebugMode = argv.inDebugMode == 'true';

let indexPath = inDebugMode ? '../public/index.html' : './public/index.html';
let mediaPath = inDebugMode ? '../src/styles/media' : './src/styles/media';

app.use('/media', express.static(mediaPath));

function* getItems() {
    let data = yield get("http://localhost:3826/api/item/getall", { gzip: true });
    return JSON.parse(data);
}

if (process.env.NODE_ENV === 'development') {
    const config = require('../webpack.config.dev.babel.js').default;
    const compiler = webpack(config);

    app.use(require('webpack-dev-middleware')(compiler, {
        noInfo: true,
        stats: {
            assets: false,
            colors: true,
            version: false,
            hash: false,
            timings: false,
            chunks: false,
            chunkModules: false
        }
    }));
    app.use(require('webpack-hot-middleware')(compiler));
} else {
    app.use(express.static(path.resolve(__dirname, '../dist')));
}

app.get(['/', '/aboutus'], function* (req, res) {
    let index = yield fs.readFile(indexPath, "utf-8");

    const initialState = {
        items: []
    };

    if (req.path == '/') {
        const items = yield getItems();
        initialState.items = items.data.items;
    }

    const history = createHistory({
        initialEntries: [req.path]
    });

    const store = getStore(history, initialState);

    if (useServerRender) {
        const appRendered = renderToString(
            <Provider store={store}>
                <ConnectedRouter history={history}>
                    <App />
                </ConnectedRouter>
            </Provider>
        );
        index = index.replace(`<%= preloadedApplication %>`, appRendered)
    } else {
        index = index.replace(`<%= preloadedApplication %>`, `Please wait while we load the application.`);
    }
    res.send(index);
});

app.listen(port, '0.0.0.0', () => {
    console.info(`Listening at http://localhost:${port}`);
    if (process.env.NODE_ENV === 'development') {
        open(`http://localhost:${port}`);
    }
});

I think if somehow we are able to use server side store in client side, we may overcome this problem.


Solution

  • It's all about store data. we should send store data somehow to client side, And use this data for store in client side.

    For me the best solution is via html:

    <script id='app-props' type='application/json'>
            <![CDATA[<%= initialData %>]]>
        </script>
    

    And in store file i retrieve it like this:

     if (typeof window !== 'undefined') {
        let initialDataEl = document.getElementById('app-props');
        try {
            let props = initialDataEl.textContent;
            props = props.replace("<![CDATA[", "").replace("]]>", "");
            defaultState = JSON.parse(props);
        }
        catch (err) {
            console.log(err);
        }
    }