DEV Community

Apcelent
Apcelent

Posted on

Migrating a realtime Checkins Discovery App from React to Preact

In this post we are going to create a client side web application to render FourSquare Checkins of a specified location per user's preference in realtime. The major technologies we are going to use is ReactJS, FourSquare API, Google Maps API.

And later down the tutorial we will see how to migrate a pre-written react app to a preact app.

Overview

React-Checkins is a realtime checkins discovery app built using Google Maps API with ReactJS.

Once rendered the app looks as below

Alt text of image

Setup and Maps Integration

We will use npm for managing all our code dependencies. First let’s setup npm in the root of our project. This will generate a file called package.json in project root. If you use the --save or --save-dev flag when installing a package, it’ll save the packages as dependencies in the package.json file. To reinstall the packages, all we need to is run npm install. The packages will be installed locally specific to our project under a directory called node_modules like virtualenv.

So, let’s generate a package.json file in our project root using npm init.

npm init
Enter fullscreen mode Exit fullscreen mode

Let’s install our first npm packages.

npm install --save react webpack babel babel-loader webpack-dev-server react-google-maps react-addons-update classnames

package.json contains several other dependencies.

npm should install the dependencies under the node_modules folder structure by default.

Create webpack config

Let us now create a webpack config. The goal of creating the config file is to concatenate all the js files.

mkdir -p src/js
touch webpack.config.js
touch dev/js/components/GoogleApp.js
Enter fullscreen mode Exit fullscreen mode

Let’s create a simple webpack config to load .jsx files using babel and More on webpack configuration here.

webpack.config file

var path = require('path');
var webpack = require('webpack');


module.exports = {
    devServer: {
        inline: true,
        contentBase: './src',
        port: 3000
    },
    devtool: 'cheap-module-eval-source-map',
    entry: './dev/js/components/GoogleApp.js', ///entry point of our app. 
    module: {
        loaders: [
            {
                test: /\.js$/,
                loaders: ['babel'],
                exclude: /node_modules/
            },
            {
                test: /\.scss/,
                loader: 'style-loader!css-loader!sass-loader'
            }
        ]
    },
    output: {
        path: 'src',
        filename: 'js/bundle.min.js'
    },
    plugins: [
        new webpack.optimize.OccurrenceOrderPlugin()
    ]
};
Enter fullscreen mode Exit fullscreen mode

At this point, our directory structure will look something like this.

root
  |-- dev
    | |--js
    |     |--components
    |         |--GoogleApp.js
    |-- node_modules
    |-- src
    | |-- js
    |   | |-- bundle.min.js   ## will create after run npm start
    |   |-- index.html
    |-- webpack.config.js
    |-- package.json
    |-- .babelrc
Enter fullscreen mode Exit fullscreen mode

The index file is fairly simple and can be found here

Initializing Maps in React Component

Import {
    withGoogleMap,
    GoogleMap,
    Marker,
    InfoWindow,
 } from "react-google-maps";
Enter fullscreen mode Exit fullscreen mode

If you're going to be loading the map through your index.html file you can include the below.

 <script src="https://maps.googleapis.com/maps/api/js?key=GOOGLE_API_KEY&callback=initMap&libraries=places"async defer></script>
Enter fullscreen mode Exit fullscreen mode

Dynamically retrieving the Google Maps Library

const GettingStartedGoogleMap = withGoogleMap(props => (
    <GoogleMap
    ref={props.onMapLoad}
    defaultZoom={3}
    options={{ minZoom: 3, maxZoom: 15 }}
    center={{ lat: 20.5937, lng: 78.9629 }}
    >
    {props.markers.map(marker => (
        <Marker
          { ...marker }
          onRightClick={() => props.onMarkerRightClick(marker)}
          onMouseOver={() => props.onMouseOver(marker)}
          >
          {marker.showInfo && (
            <InfoWindow >
              <div className="">
              {marker.info ?
                <p><b>{marker.info[0].user.firstName} says </b>
                    {marker.info[0].text}</p>
                : null}
                <p><b>Place </b> {marker.key}</p>

                </div>
              </InfoWindow>
            )}

        </Marker>

    ))}
    </GoogleMap>
));
Enter fullscreen mode Exit fullscreen mode

Getting initial state of markers

constructor(props)
        {
            super(props);
            this.state = {
                markers: [{
                    position: {
                        lat: null,
                        lng: null,
                    },
                    key: null,
                    defaultAnimation: null,
                    showInfo:false,
                    info: []
                }],
                address: '',
                results: []
            };
            this.onChange = (address) => this.setState({ address })
        }
Enter fullscreen mode Exit fullscreen mode

Search the places through the foursqaure

The searchNearPlaces function takes the response from the city search query and pushes all the results inside an array object with their respective [lat, lng] co-ordinates and pin’s icon link.

getUrl() {
    const clientID = YOUR_FOURSQUARE_CLIENT_ID,
         clientSecret = YOUR_FOURSQUARE_CLIENT_SECRET,
         version = 'v=20140806';
    let location = this.state.address,
    url = 'https://api.foursquare.com/v2/venues/explore?client_id=' +
      clientID + '&client_secret=' + clientSecret + '&near=' +
        location + '&' + version;
    return url;
}

searchNearPlaces() {
    let url = this.getUrl();
    axios.get(url)
        .then(response => {
            let results = response.data.response.groups[0].items;
            this.setState({ results:results });
            // Do with results

        })
}
Enter fullscreen mode Exit fullscreen mode

Let's save the first 20 out of total results as markers.

 var bounds = new google.maps.LatLngBounds();
 results.map((el, i) => {
    if (i < 20 ) {
    const nextMarkers = [
    ...this.state.markers,
    {
    position: {
        lat: el.venue.location.lat,
        lng: el.venue.location.lng  },
    defaultAnimation: 2,
    key: el.venue.name,
    showInfo: false,
    info: el.tips
    },
    ];
    this.setState({
    markers: nextMarkers,
    })

   bounds.extend(this.state.markers[i].position);
   }

})
this._mapComponent.fitBounds(bounds);   // bound the all markers of map
Enter fullscreen mode Exit fullscreen mode

And now we render the component

render() {
    const inputProps = {
        value: this.state.address,
        onChange: this.onChange
        type: 'search',
        placeholder: 'Search Places...',
        autoFocus: true,
    }
    const cssClasses = {
        root: 'form-group',
        input: 'form-control',
        autocompleteContainer: 'my-autocomplete-container'
    }
    const myStyles = {
        root: { position: 'absolute' },
        input: { width: '100%' },
        autocompleteContainer: { backgroundColor: 'green' },
        autocompleteItem: { color: 'black' },
        autocompleteItemActive: { color: 'blue' }
    }

    const AutocompleteItem = ({ suggestion }) => (<div>
      <i className="fa fa-map-marker"/>{suggestion}</div>)
    return (
        <div ref="map" style={{height: '600px'}}>
            <Helmet
              title="GoogleMap"
            />
            <form onSubmit={this.handleFormSubmit.bind(this)}>
                <PlacesAutocomplete
                    inputProps={inputProps}
                    classNames={cssClasses}
                    styles={myStyles}
                    autocompleteItem={AutocompleteItem}
                     />
                     <button type="submit">Submit</button>
            </form>


            <GettingStartedGoogleMap
              containerElement={
                 <div style={{ height: '650px' }} />
             }
             mapElement={
                <div style={{ height: '650px' }} />
             }
             onMapLoad={this.handleMapLoad.bind(this)}
             markers={this.state.markers}
             onMouseOver={this.handleOnMouseMarker.bind(this)}

             onMarkerRightClick={this.handleMarkerRightClick.bind(this)}
            />
        </div>
      );
  }
  /// google maps loaded with support of initMap
  window.initMap = () => {
  ReactDOM.render(
      <GettingStartedExample />,
  document.getElementById('root')
  )
}
Enter fullscreen mode Exit fullscreen mode

We will now add a funcationality to show the info of a marker on mouse hover

var react_update = require('react-addons-update');


update[index_val] = {
    $merge: {showInfo: false}
   };
   var changedMarkers = react_update(this.state.markers, update);
   this.setState({ markers: changedMarkers });
Enter fullscreen mode Exit fullscreen mode

Replacing ReactJS with PreactJS

Recently Facebook modified the license of ReactJS from BSD to BSD+patents license. Addition of patents clause means that if it felt that the product using ReactJS is competing with facebook, it's license will be revoked. So, we started looking for alternatives of ReacJS which would work seamlessly with existing react application. Preact is one such solution.

Preact is a lightweight javascript library (3 kb) which can be used as a replacement for Reactjs

Installation

preact-compat is another layer on top of preact which allows us to switch from react to preact without much changes to the existing code.

First, install preact and preact-compat modules

npm i --save preact
npm i --save preact-compat
Enter fullscreen mode Exit fullscreen mode

To use preact with webpack, we add an alias for react and react-dom in webpack.config.js file

resolve: {
        extensions: ['', '.js', '.jsx'],
        "alias": {
            "react": "preact-compat",
            "react-dom": "preact-compat"
        }
   },
Enter fullscreen mode Exit fullscreen mode

In the example, a simple react hello world application has been created. It has one component HelloWorld.js

import React, { Component } from 'react';
import ReactDOM from "react-dom";


export default class HelloWorld extends Component {
    constructor(props)
    {
        super(props);
    }


  render()
  {


    return (
          <div>
          <h1>
            Hello World!
            </h1>
          </div>
     );

  }
}

ReactDOM.render(
    <HelloWorld />,
    document.getElementById('root')
)
Enter fullscreen mode Exit fullscreen mode

Once the webpack.config.js file is updated to add alias of preact and preact-compat, application works in same way as preact-compat provides same exports as react and react-dom.

var path = require('path');
var webpack = require('webpack');

module.exports = {
    devServer: {
        inline: true,
        contentBase: './src',
        port: 3000
    },
    devtool: 'cheap-module-eval-source-map',
    entry: './dev/js/components/HelloWorld.js',
    module: {
        loaders: [
            {
                test: /\.js$/,
                loaders: ['babel'],
                exclude: /node_modules/
            },
            {
                test: /\.scss/,
                loader: 'style-loader!css-loader!sass-loader'
            }
        ]
    },
    output: {
        path: 'src',
        filename: 'js/bundle.min.js'
    },
    resolve: {
        extensions: ['', '.js', '.jsx'],
        "alias": {
            "react": "preact-compat",
            "react-dom": "preact-compat"
        }
    },
    plugins: [
        new webpack.optimize.OccurrenceOrderPlugin()
    ]
};
Enter fullscreen mode Exit fullscreen mode

We used same configuration changes to react map checkin application and the checkin discovery works as before.

Conclusion

It is a working React-Checkins app providing a fair understanding of how we can integrate Google Maps with (P)ReactJS — all in less than 15 minutes. For reference, the entire code is here

The article originally appeared on Apcelent Tech Blog.

Top comments (0)