Making Your First Webapp with React

Making Your First Webapp with React

React has gained a lot of popularity recently and has attracted a large and active community. This results in a vast wealth of reusable components for it that will save you time when coding. The library itself encourages writing loosely coupled code that is modular and composable.

In this tutorial, I will show you how to create a small application and how to split it into discrete components that talk to each other. As a base, we will take the npm-driven website example from last month, but we’ll do it the React way. It is interesting to compare the results – the React version has a few more lines of code than the jQuery version, but we can both agree that it is much better organized.

What you need to know about React

  • It is a popular client-side library/framework for building user interfaces, which is developed and used by Facebook.
  • With it, you organize your application around discrete components, with each handling its own rendering and state. Components can be nested within each other.
  • React is fast because it minimizes the number of writes to the DOM (the slowest part of any client-side application).
  • The recommended way to write React code is by using JSX – an extension to JavaScript which presents components as HTML elements. JSX needs to be compiled to JS in order to work in browsers.
  • It hasn’t hit version 1.0 as of this writing, so there might be changes in the future.
  • We have a nice article with examples for learning react which you can check out. Also there is the official getting started guide here.

What we will be building

We will create a simple web app, which invites people to search for locations and to store them in their browsers’ localStorage. The locations will be presented on a Google Map with the help of the GMaps plugin. We will use Bootstrap with the Flatly theme for the interface. In the process, we will break the application down into logical components and make them talk to each other.

Running the demo

If you don’t want to read the entire tutorial, you can go ahead and download the source code from the download button above. To run it, you need to have Node.js and npm installed. Assuming that you have, here is what you need to do:

  1. Download the zip with the source code from the button above.
  2. Extract it to a folder somewhere on your computer.
  3. Open a new terminal (command prompt), and navigate to that folder.
  4. Execute npm install. This will download and install all dependencies that are needed.
  5. Execute npm run build. This will compile the react components down to a regular JavaScript file named compiled.js.
  6. Open index.html in your browser. You should see the app.

There is one more npm command that I’ve prepared for you to make your development easier:

npm run watch

This will compile the JSX code down to JavaScript and will continue to monitor it for changes. If you change a file, the code will be recompiled automatically for you. You can see these commands in the package.json file.

The source code is easy to follow and has plenty of comments, so for those of you who prefer to read the source, you can skip the rest of the article.

Setting things up

As I mentioned, the recommended way to write React code is by using a JavaScript extension called JSX, which needs to be transformed to JavaScript. There are a few tools that can do this but the one I recommend is reactify – a browserify transform. So in addition to compiling JSX down to JavaScript, you get access to the require() node.js call and with it the ability to install and use libraries from npm.

To set up reactify, browserify and the rest, run this command:

npm install browserify reactify watchify uglify-js react

To create a production ready and minified JavaScript file, which you can put online, run this command in your terminal:

NODE_ENV=production browserify -t [ reactify --es6 ] main.js | uglifyjs > compiled.min.js

Reactify supports a limited set of the new ES6 features with the --es6 flag, which I’ve used in the source code (you will see it in a moment).

While developing, use the following command:

watchify -v -d -t [ reactify --es6 ] main.js -o compiled.js

Watchify will monitor your files for changes and recompile your source code if it is needed. It also enables source maps, so you can use the Chrome Debugger to step through your code.

Great! You can now write React modules, require() npm libraries and even use some ES6 features. You are ready for writing some code!

The code

Here are the components that we will be writing:

  • App is the main component. It contains methods for the actions that can be performed by the user like searching, adding a location to favorites and more. The other components are nested inside it.
  • CurrentLocation presents the currently visited address in the map. Addresses can be added or removed from favorites by clicking the star icon.
  • LocationList renders all favorite locations. It creates a LocationItem for each.
  • LocationItem is an individual location. When it is clicked, its corresponding address is searched for and highlighted in the map.
  • Map integrates with the GMaps library, and renders a map from Google Maps.
  • Search is a component that wraps around the search form. When it is submitted, a search for the location is triggered.
Components Breakdown

Components Breakdown

App.js

First up is App. In addition to the lifecycle methods that React requires, it has a few additional ones that reflect the main actions that can be performed by the user like adding and removing an address from favorites and searching. Notice that I am using the shorter ES6 syntax for defining functions in objects.

var React = require('react');

var Search = require('./Search');
var Map = require('./Map');
var CurrentLocation = require('./CurrentLocation');
var LocationList = require('./LocationList');


var App = React.createClass({

	getInitialState(){

		// Extract the favorite locations from local storage

		var favorites = [];

		if(localStorage.favorites){
			favorites = JSON.parse(localStorage.favorites);
		}

		// Nobody would get mad if we center it on Paris by default

		return {
			favorites: favorites,
			currentAddress: 'Paris, France',
			mapCoordinates: {
				lat: 48.856614,
				lng: 2.3522219
			}
		};
	},

	toggleFavorite(address){

		if(this.isAddressInFavorites(address)){
			this.removeFromFavorites(address);
		}
		else{
			this.addToFavorites(address);
		}

	},

	addToFavorites(address){

		var favorites = this.state.favorites;

		favorites.push({
			address: address,
			timestamp: Date.now()
		});

		this.setState({
			favorites: favorites
		});

		localStorage.favorites = JSON.stringify(favorites);
	},

	removeFromFavorites(address){

		var favorites = this.state.favorites;
		var index = -1;

		for(var i = 0; i < favorites.length; i++){

			if(favorites[i].address == address){
				index = i;
				break;
			}

		}

		// If it was found, remove it from the favorites array

		if(index !== -1){
			
			favorites.splice(index, 1);

			this.setState({
				favorites: favorites
			});

			localStorage.favorites = JSON.stringify(favorites);
		}

	},

	isAddressInFavorites(address){

		var favorites = this.state.favorites;

		for(var i = 0; i < favorites.length; i++){

			if(favorites[i].address == address){
				return true;
			}

		}

		return false;
	},

	searchForAddress(address){
		
		var self = this;

		// We will use GMaps' geocode functionality,
		// which is built on top of the Google Maps API

		GMaps.geocode({
			address: address,
			callback: function(results, status) {

				if (status !== 'OK') return;

				var latlng = results[0].geometry.location;

				self.setState({
					currentAddress: results[0].formatted_address,
					mapCoordinates: {
						lat: latlng.lat(),
						lng: latlng.lng()
					}
				});

			}
		});

	},

	render(){

		return (

			<div>
				<h1>Your Google Maps Locations</h1>

				<Search onSearch={this.searchForAddress} />

				<Map lat={this.state.mapCoordinates.lat} lng={this.state.mapCoordinates.lng} />

				<CurrentLocation address={this.state.currentAddress} 
					favorite={this.isAddressInFavorites(this.state.currentAddress)} 
					onFavoriteToggle={this.toggleFavorite} />

				<LocationList locations={this.state.favorites} activeLocationAddress={this.state.currentAddress} 
					onClick={this.searchForAddress} />

			</div>

		);
	}

});

module.exports = App;

In the render method, we initialize the other components. Each component receives only the data that it needs to get its job done, as attributes. In some places, we also pass methods which the child components will call, which is a good way for components to communicate while keeping them isolated from one another.

CurrentLocation.js

Next is CurrentLocation. This component presents the address of the currently displayed location in an H4 tag, and a clickable star icon. When the icon is clicked, the App’s toggleFavorite method is called.

var React = require('react');

var CurrentLocation = React.createClass({

	toggleFavorite(){
		this.props.onFavoriteToggle(this.props.address);
	},

	render(){

		var starClassName = "glyphicon glyphicon-star-empty";

		if(this.props.favorite){
			starClassName = "glyphicon glyphicon-star";
		}

		return (
			<div className="col-xs-12 col-md-6 col-md-offset-3 current-location">
				<h4 id="save-location">{this.props.address}</h4>
				<span className={starClassName} onClick={this.toggleFavorite} aria-hidden="true"></span>
			</div>
		);
	}

});

module.exports = CurrentLocation;

LocationList.js

LocationList takes the array with favorite locations that was passed to it, creates a LocationItem object for each and presents it in a Bootstrap list group.

var React = require('react');
var LocationItem = require('./LocationItem');

var LocationList = React.createClass({

	render(){

		var self = this;

		var locations = this.props.locations.map(function(l){

			var active = self.props.activeLocationAddress == l.address;

			// Notice that we are passing the onClick callback of this
			// LocationList to each LocationItem.

			return <LocationItem address={l.address} timestamp={l.timestamp} 
					active={active} onClick={self.props.onClick} />
		});

		if(!locations.length){
			return null;
		}

		return (
			<div className="list-group col-xs-12 col-md-6 col-md-offset-3">
				<span className="list-group-item active">Saved Locations</span>
				{locations}
			</div>
		)

	}

});

module.exports = LocationList;

LocationItem.js

LocationItem represents an individual favorite location. It uses the moment library to calculate the relative time since the location was added as a favorite.

var React = require('react');
var LocationItem = require('./LocationItem');
var moment = require('moment');

var LocationItem = React.createClass({

	handleClick(){
		this.props.onClick(this.props.address);
	},

	render(){

		var cn = "list-group-item";

		if(this.props.active){
			cn += " active-location";
		}

		return (
			<a className={cn} onClick={this.handleClick}>
				{this.props.address}
				<span className="createdAt">{ moment(this.props.timestamp).fromNow() }</span>
				<span className="glyphicon glyphicon-menu-right"></span>
			</a>
		)

	}

});

module.exports = LocationItem;

Map.js

Map is a special component. It wraps the Gmaps plugin, which is not a React component by itself. By hooking to the Map’s componentDidUpdate method, we can initialize a real map inside the #map div whenever the displayed location is changed.

var React = require('react');

var Map = React.createClass({

	componentDidMount(){

		// Only componentDidMount is called when the component is first added to
		// the page. This is why we are calling the following method manually. 
		// This makes sure that our map initialization code is run the first time.

		this.componentDidUpdate();
	},

	componentDidUpdate(){

		if(this.lastLat == this.props.lat && this.lastLng == this.props.lng){

			// The map has already been initialized at this address.
			// Return from this method so that we don't reinitialize it
			// (and cause it to flicker).

			return;
		}

		this.lastLat = this.props.lat;
		this.lastLng = this.props.lng

		var map = new GMaps({
			el: '#map',
			lat: this.props.lat,
			lng: this.props.lng
		});

		// Adding a marker to the location we are showing
		
		map.addMarker({
			lat: this.props.lat,
			lng: this.props.lng
		});
	},

	render(){

		return (
			<div className="map-holder">
				<p>Loading...</p>
				<div id="map"></div>
			</div>
		);
	}

});

module.exports = Map;

Search.js

The Search component consists of a Bootstrap form with an input group. When the form is submitted the App’s searchForAddress method is called.

var React = require('react');

var Search = React.createClass({

	getInitialState() {
		return { value: '' };
	},

	handleChange(event) {
		this.setState({value: event.target.value});
	},

	handleSubmit(event){
		
		event.preventDefault();
		
		// When the form is submitted, call the onSearch callback that is passed to the component

		this.props.onSearch(this.state.value);

		// Unfocus the text input field
		this.getDOMNode().querySelector('input').blur();
	},

	render() {

		return (
			<form id="geocoding_form" className="form-horizontal" onSubmit={this.handleSubmit}>
				<div className="form-group">
					<div className="col-xs-12 col-md-6 col-md-offset-3">
						<div className="input-group">
							<input type="text" className="form-control" id="address" placeholder="Find a location..." 
							value={this.state.value} onChange={this.handleChange} />
							<span className="input-group-btn">
								<span className="glyphicon glyphicon-search" aria-hidden="true"></span>
							</span>
						</div>
					</div>
				</div>
			</form>
		);

	}
});

module.exports = Search;

main.js

All that is left is to add the App component to the page. I am adding it to a container div with the #main id (you can see this element in index.html in the downloadable zip file).

var React = require('react');
var App = require('./components/App');

React.render(
  <App />,
  document.getElementById('main')
);

In addition to these files, I have included the GMaps library and the Google Maps JavaScript API on which it depends, as <script> tags in index.html.

We’re done!

I hope that this tutorial gave you a better understanding of how to structure React applications. There is much more you can do with the library, including server-side rendering, which we hope to cover in the future.

Presenting Bootstrap Studio

a revolutionary tool that developers and designers use to create
beautiful interfaces using the Bootstrap Framework.

Learn more
Web Browser Frame DevKit Box Mouse Cursor
by Nick Anastasov

Nick is a JavaScript programmer who loves Node and all things HTML5. He is interested in photography and is the resident food expert at Tutorialzine's office.

1Share this post
2Read one more article
3Get your free book
Book Cover
jQuery Trickshots

Tutorialzine's advanced jQuery techniques book.

Download

11 Comments

  1. Rohit Suthar says:

    It's an amazing web app!!
    Can you add auto-complete or suggest location while typing in location text field??

  2. Pavan Ratnakar says:

    Hi Guys,

    I have created a simple repo which has react integrated with server and i am using browserify for porting react code to client as well.

    Uses Grunt, Node, Less, Eslint, React on server and client (using browserify)

    https://github.com/pavanratnakar/Single-Page-App-React

    Would love if people can contribute and make it better.

    Thanks,
    Pavan

  3. Constantine Antonakos says:

    This is awesome! Please keep this up! React.js is a really interesting library, and I'd love to see more of it on this site. Thanks again!

  4. Hoàng Lê says:

    That's amazing app. I can learn a lot of from your article. Thanks Nick very much.

  5. Pavan Ratnakar says:

    https://github.com/pavanratnakar/Single-Page-App-React-Flux
    Single Page App using React, FLUX, Node.js, Atomic CSS (Atomizer), Less.js, Grunt
    Atomizer is by Yahoo! and uses radical(crazy) approach of not bloating css

  6. sean says:

    It seems odd to me, as I'm learning this. Why do you define all your functions in the "app" level and pass them around from function to function? Shouldn't you let the component handle it's functionality? It seems strange and inefficient to define one giant list of functions and pass them around.

  7. Jake says:

    When running NODE_ENV=production browserify -t [ reactify --es6 ] main.js | uglifyjs > compiled.min.js I get an error that it cannot find module 'moment' in the components directory. Bummer, I was looking forward to this.

    Also users need to globally install browserify and uglify-js, you might want to make sure people know that before doing the tutorial. Can't assume people have the exact preset dev environment as you.

    1. Morgan says:

      Hey Jake,
      try running
      $ npm install moment --save

      that should fix it!

  8. Marcus says:

    Awesome tutorial, bud! Laid it all out clean and concise.

  9. Jay says:

    Since update of react to 0.14 version, it has been separted to react and react-dom.

    Maybe you should also update your tutorial and change the souce codes as well.

    Other than that, great tutorial.

  10. Morgan says:

    Hey all!

    I created an updated version of this tutorial (https://github.com/MorganLKestner/react-location-search-app) that complies with the new version.

Add Comment

Add a Reply

HTML is escaped automatically. Surround code blocks with <pre></pre> for readability.
Perks:   **bold**   __italics__   [some text](http://example.com) for links