ReactJS

ReactJS Server Side Rendering : Calling Web Services (APIs) From Server Side

A step by step guide to support server side rendering in reactjs : Calling Web Services (APIs) From Server Side

Before learning how to implement server side rendering in reactjs , let us know why server side rendering ?

Why Server Side Rendering ?

  1. Performance gain for initial page load : Server side rendering improves the performance of initial page load, this is because it reduces number of calls from client to server. Rather than loading empty page first, then call api , get the response then iterate the response to create ui components at client side, for server side rendering, many client-server calls will be removed and for the first render itself the client will get complete HTML with all the data filled. So the browser will just render quickly the final HTML. See the below image taken from Udemy tutorial on ReactJS, which gives complete flow diagram of client-server requires

 

Image Credit – Udemy Server Side Rendering React JS Flow Diagram

2. SEO friendly : Search Engine crawlers will look for server side rendering because it makes sense for crawlers to communicate with your server to get details of your page ?

3. Social Media Sharing: to get the preview of your page when some one share your page in social media like facebook, server side rendering is must, because facebook/twitter/g+ needs og tags to be filled for the first render itself

After knowing importance of server side rendering, let us explore how to enable it using reactjs.

Important Considerations while working on server side rendering

  1. In reactjs if you are dealing with server side rendering, then you should be aware that most of the component life cycle methods will not be called at server side. if you have to do some operations then you should either do it in component’s constructor or componentWillMount 
  2. And you should keep updating redux store from server side as well and give the updated store to client other wise client is unaware of what is changed in the store

How To Enable Server Side Rendering in React : Calling APIs from server side

Prerequisites for server side rendering in reactjs

  1. Having a nodejs middleware for request handling – if your client part is running on 8080 port and api’s are running on 8081 port, then all the requests coming to 8080 port, should go through this middleware. This is where server side api calls will be handled for every route.
  2. We need a Redux Action for API call which will just return a promise – note here is that, this should not dispatch the state, since its called from server side, the dispatch doesn’t make any sense here.
  3. Static function pointer in a component to redux action -> function pointer will be used to call action from server side
  4. Creating redux store at server side to update the data
  5. Keeping a global state and including it in view ( index.ejs or index.pug whichever view engine you use)

Let us see how to implement server side rendering with reactjs

Define request handler middleware in your app.js : this is where we call apis from backend , observe and understand how we call api for respective route. this is common middleware for all routes, but calling right API for respective route is happening due to routesConfig and static function pointer defined in respective component of routes. To understand this completely check how fetchData function is being used in function handleRender(req, res) and NewsDetailComponent

//App.js

// REQUEST HANDLER FOR SERVER-SIDE RENDERING
const requestHandler = require('./requestHandler');
// requestHandler.js

'use strict';

import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { renderToString } from 'react-dom/server';
import { StaticRouter, matchPath } from 'react-router-dom';

//Reducers combiner 
import reducers from '../src/client/reducers/index';

//All the routes defined 
import routes from '../src/routes';

// Routes config which just has path and respective component mapping
import routesConfigs from '../src/routesConfig';


import DocumentMeta from 'react-document-meta';

//A Wrapper for axios where actual api call happens
import { HTTPRequestHandler } from '../src/util/commonRequires';


function renderView(req, res, state) {
    // STEP-1 CREATE A REDUX STORE ON THE SERVER
    const store = createStore(reducers, state);

  // STEP-2 GET INITIAL STATE FROM THE STORE
  const initialState = JSON.stringify(store.getState()).replace(/<\/script/g, '<\\/script').replace(/<!--/g, '<\\!--');
  // STEP-3 IMPLEMENT REACT-ROUTER ON THE SERVER TO INTERCEPT CLIENT REQUESTs AND DEFINE WHAT TO DO WITH THEM
  const context = {};
  const reactComponent = renderToString(
    <Provider store={store}>
      <StaticRouter
        location={req.url}
        context={context}>
        {routes}
      </StaticRouter>
    </Provider>
  );
  const reactMetaComponent = DocumentMeta.renderToStaticMarkup();

  if (context.url) {
    // can use the `context.status` that
    // we added in RedirectWithStatus
    redirect(context.status, context.url);
  } else {
    //https://crypt.codemancers.com/posts/2016-09-16-react-server-side-rendering/
    res.status(200).render('index', { reactComponent, reactMetaComponent, initialState });
  }
}
function handleRender(req, res) {
  const components = routesConfigs
    .filter( route => matchPath( req.path, route ) ) // filter matching paths
    .map( route => route.component ); // check if components have data requirement
    let promiseObj = null;    
    if (components.length > 0 && (components[0].fetchData instanceof Function)) {
      components[0]
      .fetchData(req.query)
      .then((response) => {
        //console.log('***--- response ', response);
          renderView(req, res, response);
      })
      .catch((error) => {
        console.log('***--- error ', error);
        renderView(req, res, {});
      });
    } else {
      renderView(req, res, {});
    }
}

module.exports = handleRender;

Check the view part and the most important part because this is where the global redux stare is being shared by client and server
// View Part : index.ejs 
// put below lines of code within the <body> tag

<DIV class = 'appStyle' id="app"><%-reactComponent-%></DIV>
<script>window.INITIAL_STATE=<%- initialState -%></script>

 

Now let us define a component which embed all the routes

// PrimaryLayout.js

import React from 'react';
import { Route, Switch } from 'react-router-dom';
import DocumentMeta from 'react-document-meta';

import MenuComponent from '../components/common/elements/menu';
import NewsDetailComponent from '../components/pages/NewsDetailComponent';

import {
  NEWS
} from '../../constants';


class PrimaryLayout extends React.Component {
  render() {
    const layoutPath = this.props.match.path;
    return (
      <div style={{ paddingTop: 80 }}>
        <MenuComponent />
        <main>
          <Switch>
            <Route exact path={layoutPath} component={LandingPageComponent} />
            <Route exact path={`${layoutPath}${NEWS}/:newsId`} component={NewsDetailComponent} />
          </Switch>
        </main>
      </div>
    );
  }
}

export default PrimaryLayout;

 

Now let us define all routes

//routes.js

'use strict';
// REACT
import React from 'react';

// REACT-ROUTER
//import {Router, Route, IndexRoute, browserHistory} from 'react-router';
import { Route, Switch } from 'react-router-dom';

import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { MuiThemeProvider } from 'material-ui/styles';

import PrimaryLayout from './client/containers/PrimaryLayout';


// RETRIVES COMPONENTS BASED ON STATUS
const Status = function ({ code, children }) {
  return (
        <Route
          render={function ({ staticContext }) {
            if (staticContext) {
              staticContext.status = code;
            }
            return children;
          }}
        />
    );
};

//NOT-FOUND COMPONENT
const NotFound = function () {
    return (
      <Status code={404}>
        <div>
          <h2> Sorry, can’t find this page</h2>
        </div>
      </Status>
    );
};

// CLIENT-SERVER SHARED ROUTES
const routes = (
  <MuiThemeProvider muiTheme={getMuiTheme('lightBaseTheme')}>
      <div className='appStyle'>
          {/*
            switch required to handle inclusive rendering,
            example : two different paths like /about /:userName both
            will render both the components switch handles such requests by
            inclusively rendering the specific component
         */}
          <Switch>
            <Route path="/notfound" component={NotFound} />
            <Route path="/" component={PrimaryLayout} />
          </Switch>
      </div>
    </MuiThemeProvider>
    );

export default routes;

 

and let us use the routes

// client.js which defines routes 

'use strict';
// REACT
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
// REACT-ROUTER
import { BrowserRouter } from 'react-router-dom';
//import {Router, Route, IndexRoute, browserHistory} from 'react-router';

import { applyMiddleware, createStore } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import routes from '../routes';


// IMPORT COMBINED REDUCERS
import reducers from './reducers/index';
// STEP 1 create the store
const middleware = applyMiddleware(thunk, logger);
// WE WILL PASS INITIAL STATE FROM SERVER STORE
const initialState = window.INITIAL_STATE;
const store = createStore(reducers, initialState, middleware);

const Routes = (
  <Provider store={store}>
    {/*
      Provider
      Makes the Redux store available to the connect() calls in the component hierarchy below.
      Normally, you can’t use connect() without wrapping a parent or ancestor
      component in
   */}
    {/*
      A <Router> that uses the HTML5 history API (pushState, replaceState and the popstate event)
      to keep your UI in sync with the URL.
    */}
    <BrowserRouter>
      {routes}
    </BrowserRouter>
  </Provider>
);

render(
  Routes, document.getElementById('app')
);

 

New Let us define our component

'use strict';
import React from 'react';
import {connect} from 'react-redux';

import { fetchNews, fetchNewsData } from '../actions/newsActions';

 class NewsDetailComponent extends React.Component {
    constructor(props) {
      super(props);
    }
    
    componentDidMount() {
        this.props.fetchNews(this.props.match.params.eventId);
    }
    render() {
        return(
            <div className='container-fluid'>
                <section>
                    <div className='row'>
                        <div className='col-lg-2 col-md-2 col-sm-2 col-xs-1'></div>
                        <div className='col-lg-8 col-md-8 col-sm-8 col-xs-10'>
                           {/* here your news component */} 
                        </div>
                        <div className='col-lg-2 col-md-2 col-sm-2 col-xs-1'></div>
                    </div>
                </section>
            </div>
        );
    }
 }


function mapStateToProps(state) {
    return {
        newsInfo: state.newsInfo.newsInfo
    };
}
NewsDetailComponent.fetchData = fetchNewsData;
export default connect(mapStateToProps, {
    fetchNews,
})(NewsDetailComponent);

And redux action which calls API

export const fetchNewsData = (params) => {
    return new Promise((resolve, reject) => {
        const newsDetailsUrl = `${NEWS_API_PATH}/${params.newsId}?newsId=${newsId}`;
        HTTPRequestHandler
        .get(newsDetailsUrl)
        .then((apiResponse) => {
            / * 
                Return the updated state so that we can update redux state 
                from server side
            */
            resolve({
                    news: { 
                        news: { 
                            newsDetails: apiResponse.data.newsDetails,
                        } 
                    }
                  });
            
        })
        .catch((apiError) => {
            console.log('-- 2 api call fail--- ', apiError);
            reject(null);
        });
    });
}

Rather than explaining each line of code written, I would like to help you to understand it by taking your attention on important aspects 

  1. to understand how the redux state is managed from backend and available for front end ( UI component, NewsDetailComponent) check how initialState and window.INITIAL_STATE are being used 
  2. to understand how api is called in a common middleware requestHandler at server side for respective page please check how routesConfig , fetchData, fetchNewsData are being used 
  3. to understand how redux store is managed even at server side and synched with client side which is most important – other wise client is unaware of what happened at server side – check function renderView(req, res, state) (server side) and client.js (client side) both are creating redux store but the data from server is being used by client using a global store object.

References : 

https://medium.freecodecamp.org/demystifying-reacts-server-side-render-de335d408fe4

http://sujinc.com/2018/06/20/all-about-sujinc-com-part-1-reactjs-ssr/

 

ReactJS : Functional or Stateless components

ReactJS : Functional or Stateless components

Many times you might come across a situation where React Component doesn’t need any state, which we call as stateless components. Thanks to React0.14 update, It introduced new types of react components with no state. 

React Stateless components are very much needed when the components is readonly doesn’t perform any operation other than just showing information.

How to Write React Functional component ?

React Functional Component Example: for stateless react component or a functional react component

const MessageBar = function(props) {
  return <p>Dear {props.username} you have completed the task successfully</p>;
};

The same component can nicely be written in one line using ES2015 standard as follows

const MessageBar = ({ username }) => <p>Dear {username} user, you have successfully completed the task</p>;

 

Conceptually components are like javascript functions here, they accept inputs as props, and return react elements. Functional components wherever required will also reduce load on redux since it is not required to maintain state for these components.

For more information on React functional components visit facebook react official site 

Check Advantages of React Functional Components

Also Check How to integrated Google Maps in reactJS : http://knowledge-cess.com/reactjs-google-map-component/

 

ReactJS: Google Map Component

ReactJS: Google Map Component

Steps to implement google maps in Facebook’s ReactJS Web Application

1. Add npm module in your project for react-google-maps

npm install --save react-google-maps # or
yarn add react-google-maps

 

Reference : https://github.com/tomchentw/react-google-maps

Before this you have to include google maps api sdk in your index.ejs file 

<script src=”https://maps.googleapis.com/maps/api/js?v=3.27&libraries=places,geometry&key=<YOUR_API_KEY>”></script>

How to get Google Maps Api Key ? 

Follow the steps given in this link

2. Create Google Map Handler Component

'use strict';

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import update from 'react-addons-update';

import canUseDOM from 'can-use-dom';

import raf from 'raf';
import GoogleMapComponent from './gMapComponent';

const geolocation = (
  canUseDOM &amp;&amp; navigator.geolocation ?
  navigator.geolocation :
  ({
    getCurrentPosition(success, failure) {
      failure('Your browser doesnt support geolocation.');
    },
  })
);

class GoogleMapHandlerComponent extends React.Component {

    state = {
      bounds: null,
      center: null,
      content: null,
      radius: 400,
      markers: []
    };

  constructor(props) {
    super(props);
    if (props !== null &amp;&amp;
        props !== undefined &amp;&amp;
        props.location !== null &amp;&amp;
        props.location !== undefined &amp;&amp;
        props.location.markers !== null &amp;&amp;
        props.location.markers !== undefined) {
      const locationInfo = {
        center: props.location.center,
        markers: props.location.markers
      };
      this.state = locationInfo;
    }
  }

  componentDidMount() {
    const tick = () =&gt; {
      if (this.isUnmounted) {
        return;
      }
      this.setState({ radius: Math.max(this.state.radius - 5, 0) });
      if (this.state.radius &gt; 10) {
        raf(tick);
      }
    };
    geolocation.getCurrentPosition((position) =&gt; {
      if (this.isUnmounted) {
        return;
      }
      this.setState({
        center: {
          lat: position.coords.latitude,
          lng: position.coords.longitude,
        },
        content: 'Location found using HTML5.',
      });

      raf(tick);
    }, (reason) =&gt; {
      if (this.isUnmounted) {
        return;
      }
      this.setState({
        center: {
          lat: 60,
          lng: 105,
        },
        content: 'Error: The Geolocation service failed (${reason}).',
      });
    });

    if (document.getElementsByClassName('pac-container').length &gt; 0) {
      const gmapSearchResultsBox = document.getElementsByClassName('pac-container')[0];
      gmapSearchResultsBox.style.zIndex = '2000';
    }
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps !== null &amp;&amp;
        nextProps !== undefined &amp;&amp;
        nextProps.location !== null &amp;&amp;
        nextProps.location !== undefined &amp;&amp;
        nextProps.location.markers !== null &amp;&amp;
        nextProps.location.markers !== undefined) {
      this.setState({
        center: nextProps.location.center,
        markers: nextProps.location.markers
      });
    }
  }

  componentDidUpdate() {
    if (document.getElementsByClassName('pac-container').length &gt; 0) {
      const gmapSearchResultsBox = document.getElementsByClassName('pac-container')[0];
      gmapSearchResultsBox.style.zIndex = '2000';
    }
  }
  componentWillUnmount() {
    this.isUnmounted = true;
  }

  isUnmounted = false;

 handleMapMounted = this.handleMapMounted.bind(this);
 handleBoundsChanged = this.handleBoundsChanged.bind(this);
 handleSearchBoxMounted = this.handleSearchBoxMounted.bind(this);
 handlePlacesChanged = this.handlePlacesChanged.bind(this);
 handleMarkerClose = this.handleMarkerClose.bind(this);
 handleMarkerClick = this.handleMarkerClick.bind(this);

 handleMapMounted(map) {
     this._map = map;
   }

   handleBoundsChanged() {
     this.setState({
       bounds: this._map.getBounds(),
       center: this._map.getCenter(),
     });
   }

   handleSearchBoxMounted(searchBox) {
     this._searchBox = searchBox;
   }
   handleLoadPlaces(markers) {

   }
   handlePlacesChanged() {
     const places = this._searchBox.getPlaces();

     // Add a marker for each place returned from search bar
     const markers = places.map(place =&gt; ({
       position: place.geometry.location,
     }));

     // Set markers; set map center to first search result
     const mapCenter = markers.length &gt; 0 ? markers[0].position : this.state.center;
     this.setState({
       center: mapCenter,
       markers,
     });
     this.props.handleLocationChange(mapCenter, places);
   }

   // Toggle to 'true' to show InfoWindow and re-renders component
  handleMarkerClick(targetMarker) {
    this.setState({
      markers: this.state.markers.map(marker =&gt; {
        if (marker === targetMarker) {
          if (marker.showInfo) {
            return {
              ...marker,
              showInfo: false,
            };
          }
          return {
            ...marker,
            showInfo: true,
          };
        }
        return marker;
      }),
    });
  }

  handleMarkerClose(targetMarker) {
    this.setState({
      markers: this.state.markers.map(marker =&gt; {
        if (marker === targetMarker) {
          return {
            ...marker,
            showInfo: false,
          };
        }
        return marker;
      }),
    });
  }

  render() {
    return (
      &lt;GoogleMapComponent
        containerElement={
          &lt;div style={{ height: '100%' }} /&gt;
        }
        mapElement={
          &lt;div style={{ height: '100%' }} /&gt;
        }
        center={this.state.center}
        onMapMounted={this.handleMapMounted}
        onBoundsChanged={this.handleBoundsChanged}
        onSearchBoxMounted={this.handleSearchBoxMounted}
        bounds={this.state.bounds}
        onPlacesChanged={this.handlePlacesChanged}
        onMarkerClick={this.handleMarkerClick}
        onMarkerClose={this.handleMarkerClose}
        markers={this.state.markers}
      /&gt;
    );
  }
}

 function mapDispatchToProps(dispatch) {
   return bindActionCreators({ }, dispatch);
 }

export default connect(null, mapDispatchToProps)(GoogleMapHandlerComponent);

 

3. Create Google Map Component, this component uses the components of react-google-maps module

'use strict';

import React from 'react';
import IconButton from 'material-ui/IconButton';

import {
  withGoogleMap,
  GoogleMap,
  Circle,
  InfoWindow,
  Marker
} from 'react-google-maps';

import SearchBox from 'react-google-maps/lib/places/SearchBox';
import GoogleMapMarkerInfoWindow from './gMapMarkerInfoWindow';

import layoutStyle from '../../../constants/layoutStyle';

const INPUT_STYLE = {
  boxSizing: 'border-box',
  MozBoxSizing: 'border-box',
  border: '1px solid transparent',
  width: '240px',
  height: '32px',
  marginTop: '27px',
  marginRight: '20px',
  padding: '0 12px',
  borderRadius: '1px',
  boxShadow: '0 2px 6px rgba(0, 0, 0, 0.3)',
  fontSize: '14px',
  outline: 'none',
  textOverflow: 'ellipses',
};

const GoogleMapComponent = withGoogleMap(props =&gt; (
  &lt;GoogleMap
    ref={props.onMapMounted}
    defaultZoom={15}
    center={props.center}
    onBoundsChanged={props.onBoundsChanged}
  &gt;
    &lt;SearchBox
      ref={props.onSearchBoxMounted}
      bounds={props.bounds}
      controlPosition={google.maps.ControlPosition.TOP_RIGHT}
      onPlacesChanged={props.onPlacesChanged}
      inputPlaceholder='Search Location'
      inputStyle={INPUT_STYLE}
    /&gt;

    {props.markers.map((marker, index) =&gt; (
      &lt;Marker
        key={index}
        position={marker.position}
        onClick={() =&gt; props.onMarkerClick(marker)}
      &gt;
        {/*
           Show info window only if the 'showInfo' key of the marker is true.
           That is, when the Marker pin has been clicked and 'onCloseClick' has been
           Successfully fired.
        */}
        {marker.showInfo &amp;&amp; (marker.pinOnly === null ||
        marker.pinOnly === undefined || !marker.pinOnly) &amp;&amp; (
          &lt;InfoWindow onCloseClick={() =&gt; props.onMarkerClose(marker)}&gt;
            &lt;GoogleMapMarkerInfoWindow infoContent={marker.infoContent} /&gt;
          &lt;/InfoWindow&gt;
        )}
      &lt;/Marker&gt;
    ))}

    {props.center &amp;&amp; (
      &lt;Circle
        center={props.center}
        radius={props.radius}
        options={{
          fillColor: 'red',
          fillOpacity: 0.20,
          strokeColor: 'red',
          strokeOpacity: 1,
          strokeWeight: 1,
        }}
      /&gt;
    )}
  &lt;/GoogleMap&gt;
));

export default GoogleMapComponent;


4. Use Google Map Handler component whereever you wish to place the map

render() {
      const handleLocationChange = this.handleLocationChange;
      const mapsComponent = (
        &lt;GoogleMapHandlerComponent
          location={this.props.location}
          handleLocationChange={handleLocationChange.bind(this)}
        /&gt;);
      return (
        &lt;div
          id='mapId'
          style={{ width: '450px', height: '350px' }}
        &gt;
          {mapsComponent}
        &lt;/div&gt;
      );
    }

Note you have to pass your current location to land user on his current location, else any other location which is default location.
and a callback for handling change in location.

ReactJS google map location change callback will be called by react-google-maps library when there is change in location,
like user searched some location

Also check stateless react components : http://knowledge-cess.com/reactjs-functional-or-stateless-components/