Monday, August 15, 2016

Data Persistence and Sessions With React


Having a “remember me” function is a very useful feature, and implementation with React and Express is relatively easy. Continuing from our last part where we set up a WebRTC chat application, we will now add Mongo-backed persistent sessions and a database-backed online user list for good measure.

Sessions?

If you have not used sessions before, in brief they are pretty similar to cookies, in that sessions enable you to track the active users of your application in real time. Sessions actually work via a session cookie, which is sent in the request / response headers from your app.

So cookies and sessions are intertwined by nature. Then why do we need sessions if we already have cookies? What sessions give you in addition is the ability to define the back-end storage used by the server part of your application. This means that whenever the information is required by your app, it can be retrieved from the database.

So in a real-life example for our chat application, we can now store the username of the user—and if we reconfigured our app somewhat, also insert the entire chat history into the database for logging.

In this next example we will use a Mongo database for persistent back-end storage. This is one of several options available for session storage, and another I highly recommend for larger-scale production setups with multiple web servers is memcache.

Document Storage

Mongo is a NoSQL document storage engine rather than a relational data store such as the popular MySQL. NoSQL is really easy to get your head around if you are coming from MySQL or similar databases and need to get up to speed with Mongo—it won’t take you long. The biggest differences you should know are the following:
  • As the name says, NoSQL doesn’t use SQL to perform queries. Instead the data is abstracted with method calls; for example db.collectionName.find() would be a SELECT * FROM table.
  • Terminology is different: in MySQL we have Tables, Rows and Columns, and in Mongo it’s Collections, Documents and Keys.
  • Data is structured, the same as a JSON object.
If you do not have Mongo yet, please install it via your package manager. In Linux-based distributions, for example:
  1. $ sudo apt-get install mongodb
Once we have Mongo installed, we can easily add Mongo support to our chat application with the mongoose module available from npm. Install mongoose with the following:
  1. $ npm install mongoose --save
Now let’s add some Mongo to our app. Fire up your code editor, and open app.js and set the top of your script to be as follows.
  1. //Configure our Services
  2. var PeerServer = require('peer').PeerServer,
  3.     express = require('express'),
  4.     mongoose = require('mongoose'),
  5.     assert = require('assert'),
  6.     events = require('./src/events.js'),
  7.     app = express(),
  8.     port = process.env.PORT || 3001;
  9.  
  10. //Connect to the database
  11. mongoose.connect('mongodb://localhost:27017/chat');
  12. var db = mongoose.connection;
  13.  
  14. mongoose.set('debug', true);
  15.  
  16. db.on('error', console.error.bind(console, '# Mongo DB: connection error:'));
We include mongoose with require('mongoose') and then utilise our database connection via mongoose.connect('mongodb://localhost:27017/chat');.

The /chat defines the name of the database we are connecting to.

Next, for development purposes, I recommend we set the debugging to on.
  1. mongoose.set('debug', true);
Finally we add a handler for any error events:
  1. db.on('error', console.error.bind(console, '# Mongo DB: connection error:'));
Next you can add your check for the connection with the following code:
  1. db.once('open', function (callback) {
  2.   console.log("# Mongo DB:  Connected to server");
  3. }
The way that mongoose is used is that once the db instance receives the open event, we will enter into execution for our mongo connection. So we will need to wrap our existing code into this new mongo connection in order to utilise it.

Here is a full code listing with mongoose added and inserting rows and deleting them as users come online and go offline.
  1. //Configure our Services
  2. var PeerServer = require('peer').PeerServer,
  3.     express = require('express'),
  4.     mongoose = require('mongoose'),
  5.     assert = require('assert'),
  6.     events = require('./src/events.js'),
  7.     app = express(),
  8.     port = process.env.PORT || 3001;
  9.  
  10. //Tell express to use the 'src' directory
  11. app.use(express.static(__dirname + '/src'));
  12.  
  13. //Connect to the database
  14. mongoose.connect('mongodb://localhost:27017/chat');
  15. var db = mongoose.connection;
  16.  
  17. mongoose.set('debug', true);
  18.  
  19. db.on('error', console.error.bind(console, '# Mongo DB: connection error:'));
  20.  
  21. db.once('open', function (callback) {
  22.  
  23.   console.log("# Mongo DB:  Connected to server");
  24.  
  25.   //Setup our User Schema
  26.   var usersSchema = mongoose.Schema({username: String});
  27.   var User = mongoose.model('User', usersSchema);
  28.  
  29.   //Configure the http server and PeerJS Server
  30.   var expressServer = app.listen(port);
  31.   var io = require('socket.io').listen(expressServer);
  32.   var peer = new PeerServer({ port: 9000, path: '/chat' });
  33.  
  34.   //Print some console output
  35.   console.log('#### -- Server Running -- ####');
  36.   console.log('# Express:   Listening on port', port);
  37.  
  38.   peer.on('connection', function (id) {
  39.     io.emit(events.CONNECT, id);
  40.     console.log('# Connected: ', id);
  41.  
  42.     //Store Peer in database
  43.     var user = new User({ username: id });
  44.     user.save(function (err, user) {
  45.       if (err) return console.error(err);
  46.       console.log('# User '+ id + ' saved to database');
  47.     });
  48.  
  49.   });
  50.  
  51.   peer.on('disconnect', function (id) {
  52.     io.emit(events.DISCONNECT, id);
  53.     console.log('# Disconnected: ', id);
  54.  
  55.     //Remove Peer from database
  56.     User.remove({username: id}, function(err){ if(err) return console.error(err)});
  57.  
  58.   });
  59.  
  60. });
To see this working, let’s fire up the chat application. Just run npm start to get ‘er up.

Now connect to the chat as normal inside the browser (default at http://localhost:3001).

Once you have connected to your chat, in a new terminal window open mongo chat to enter the mongo cli.
  1. $ mongo chat
  2. MongoDB shell version: 2.0.6
  3. connecting to: chat
  4. > db.users.find()
  5. { "username" : "CameronLovesPigs", "_id" : ObjectId("5636e9d7bd4533d610040730"), "__v" : 0 }
Here you have the document record stored inside your mongo, and now you can always check how many users are online by running at the mongo prompt db.users.count().
  1. > db.users.count()
  2. 3
Adding Sessions to Our App

Because we used Express to build our application, this part is really pretty simple and just requires the installation of a couple modules from npm to get us going.

Get the express-session and connect-mongo packages from npm:
  1. $ npm install express-session connect-mongo cookie-parser --save
Now include them in the top of app.js:
  1. var PeerServer = require('peer').PeerServer,
  2.     cookieParser = require('cookie-parser'),
  3.     express = require('express'),
  4.     session = require('express-session'),
  5.     mongoose = require('mongoose'),
  6.     MongoStore = require('connect-mongo')(session),
  7.     //...
After you set up mongoose.connect you can configure sessions with express. Change your code to the following; you can specify your own secret string.
  1. //Connect to the database
  2. mongoose.connect('mongodb://localhost:27017/chat');
  3. var db = mongoose.connection;
  4.  
  5. mongoose.set('debug', true);
  6.  
  7. db.on('error', console.error.bind(console, '# Mongo DB: connection error:'));
  8.  
  9. app.use(cookieParser());
  10. app.use(session({
  11.   secret: 'supersecretstring12345!',
  12.   saveUninitialized: true,
  13.   resave: true,
  14.   store: new MongoStore({ mongooseConnection: db })
  15. }))
Here a crucial setting to note is the saveUninitialized: true inside the last app.use. This will ensure the sessions are saved.

We specify where with the store property, which we set to the MongoStore instance, specifying which connection to use via mongooseConnection and our db object.

To store to the session, we need to use express for the request handling because we need access to the request value, for example:
  1. //Start persistent session for user
  2. app.use(function(req, res, next) {
  3.     req.session.username = id;
  4.     req.session.save();
This will create the req.session.username variable with the value being entered by the user and save it for later.

Next, we can check for this value with our client-side code and automatically log the user in when they refresh so that they never get signed out of the chat and are automatically logged in as their chosen username.

Also interesting to note, because we have database-backed sessions, is that in the event of the developers changing the application and the back-end reloading, the users logged in to their clients will stay logged in as the session store is now persistent. This is a great feature to keep your users happy and logged in all the while you are developing, or if there’s a disconnection from an unstable client.

Persistent Login

Now that we have the session cookie part set up, let’s work on adding persistent login to our front-end code.

So far we have just used the default route provided by Express for an SPA application, and not defined any route handling for Express. As I mentioned before, to get access to the session you will need Express’s request / response variables.

First we need one route so we can access the request object Express provides and display it for debugging purposes. Inside your Express configuration in /app.js, add the following near the top of the file, after the session setup:
  1. app.use(session({
  2.   secret: 'supersecretstring12345!',
  3.   saveUninitialized: true,
  4.   resave: true,
  5.   store: new MongoStore({ mongooseConnection: db })
  6. }))
  7.  
  8. app.get('/', function (req, res) {
  9.   res.sendFile(__dirname +'/src/index.html');
  10.   if(req.session.username == undefined){
  11.     console.log("# Username not set in session yet");
  12.   } else {
  13.     console.log("# Username from session: "+ req.session.username);
  14.  
  15.   }
  16. });
Now we have some basic logging to see what is happening with our session value. In order to set it, we need to configure getter and setter routes like so:
  1. //Save the username when the user posts the set username form
  2. app.post('/username', function(req, res){
  3.   console.log("# Username set to "+ req.body.username);
  4.   req.session.username = req.body.username;
  5.   req.session.save();
  6.   console.log("# Session value set "+ req.session.username);
  7.   res.end();
  8. });
  9.  
  10. //Return the session value when the client checks
  11. app.get('/username', function(req,res){
  12.   console.log("# Client Username check "+ req.session.username);
  13.   res.json({username: req.session.username})
  14. });
These two routes will function as the get and set for the username session var. Now with a bit of basic JavaScript we can implement autologin for our app. Open up src/App.js and change it as follows:
  1. /* global EventEmitter, events, io, Peer */
  2. /** @jsx React.DOM */
  3.  
  4. $(function () {
  5.   'use strict';
  6.  
  7.   // Check for session value
  8.   $(document).ready(function(){
  9.     $.ajax({
  10.           url: '/username'
  11.     }).done(function (data) {
  12.       console.log("data loaded: " + data.username);
  13.       if(data.username)
  14.         initChat($('#container')[0], data.username);
  15.     });
  16.   });
  17.  
  18.   // Set the session
  19.   $('#connect-btn').click(function(){
  20.     var data = JSON.stringify({username: $('#username-input').val()});
  21.     $.ajax({ url: '/username',
  22.               method: "POST",
  23.               data: data,
  24.               contentType: 'application/json',
  25.               dataType: 'json'
  26.             });
  27.   });
  28.  
  29.   // Initialize the chat
  30.   $('#connect-btn').click(function () {
  31.     initChat($('#container')[0],
  32.       $('#username-input').val());
  33.   });
  34.  
  35.   function initChat(container, username) {
  36.     var proxy = new ChatServer();
  37.     React.renderComponent(<ChatBox chatProxy={proxy}
  38.       username={username}></ChatBox>, container);
  39.   }
  40.  
  41.   window.onbeforeunload = function () {
  42.     return 'Are you sure you want to leave the chat?';
  43.   };
  44.  
  45. });
With the $.ajax facility of jQuery we create a request to check the value of the session variable when the document becomes available. If it is set, we then initialise our React component with the stored value, resulting in an autologin feature for our users.

Fire up the chat again with npm start and have a look in your browser to see the sessions working.

Conclusions

Now you have seen how easy it is to use Mongoose in conjunction with Express and set up Express sessions. Taking your application development further with React as the view controller linked to database-backed elements will create some serious applications.
Written by Tom Whitbread

If you found this post interesting, follow and support us.
Suggest for you:

Modern React with Redux

Advanced React and Redux

Build Apps with React Native

React for Visual Learners

The Complete Web Development Tutorial Using React and Redux

Saturday, August 13, 2016

How to Create a News Reader With React Native: Web Page Component_part 2 (end)

3. Running the App

To run the app, you need an Android device or an emulator. If you want to use an emulator, I recommend using Genymotion. You can run the app by executing the following command:
  1. react-native run-android
This command installs and launches the app. But you will get the following error if you try to do so

This is because React Native expects the React server to be running on your machine. The React server compiles the app every time you save the changes in your text editor. The react-native run-android command is only used for running the app for the purpose of testing and debugging the app. That's why it's dependent on the React server for actually compiling the app.

To get rid of the error, you need to run the react-native start command to start the server. This takes a while on the first run, but when it gets to the part where it says the following:
  1. <END>   Building Dependency Graph (35135ms)
You can open a new terminal window on your project directory and execute adb shell input keyevent 82. This opens the developer menu in the device or emulator. Once the menu is opened, select dev settings then select debug server host & port.

This opens a prompt asking you to enter the ip address and port of your computer. Find out the internal IP address of your computer and enter it in the prompt along with the port 8081, which is the default port on which React server runs. In other words, if your IP address is 192.168.254.254, then enter 192.168.254.254:8081.

After that, go back to the developer menu and select reload JS. This reloads the app so it detects the running React server instance. The app should how be working without problems.

If you want to test on an iOS device, then follow the guide on the React Native website.

4. Next Steps

We have built a pretty neat news reader app with React Native. What's next? Here are a few ideas if you want to learn more about React Native:
  • Improve the code by breaking the app down in a few more reusable components. Start by looking at duplicated code. For example, in the app that we created, we have duplicated the header and the components inside it. What you can do is create a header component that accepts the title as the property and then requires it on every page where you need a header.
  • Improve the response time of the app by creating a server that caches the items from the Hacker News API. This allows you to perform only one network request that contains all the news items instead of having to perform multiple network requests like we did in this tutorial.
  • Generate a signed APK so that you can distribute the app on Google Play. For iOS, you can use Xcode to distribute your app to Apple's App Store.
  • Explore the documentation for APIs that access native device capabilities, such as the camera.
  • Check out the Awesome React Native repo on Github. That repo contains a list of components, resources, and tools that you can use with React Native.
  • If you want to keep yourself updated with news about React Native, then subscribe to the React Native Newsletter.
Conclusion

That's it. In this tutorial, you learned how to work with React Native to create a news reader app that talks to the Hacker News API.
Written by Wernher-Bel Ancheta

If you found this post interesting, follow and support us.
Suggest for you:

Modern React with Redux

Advanced React and Redux

Build Apps with React Native

React for Visual Learners

The Complete Web Development Tutorial Using React and Redux

Thursday, August 11, 2016

How to Create a News Reader With React Native: Web Page Component_part 1


In the first part , you learned how to set up React Native on your machine, create and use custom components, and use third party libraries, such as moment.js. In this tutorial, you learn how to make network requests using fetch, render a web page using the built-in WebView component, and run the app on a physical device.

1. Fetch API Wrapper

In the first part of this series, we used the api function, but we haven't defined it yet. Start by creating a src directory and add a file to it, api.js. Open the file and add the following to it:
  1. module.exports = function(url){
  2.      
  3.     return fetch(url).then(function(response){
  4.         return response.json();
  5.     }).then(function(json){
  6.         return json;
  7.     });
  8. }
This file uses the fetch function, which is by default available in React Native. This function allows the app to perform network requests. If you've used jQuery, it's pretty similar to the $.ajax function. You specify a URL and some optional data, and you get a response back.

The only difference is that you need to do a bit of extra work. The function for capturing the first promise returns the raw response, which means that you have to call the json method on the response to get the promise that returns the JSON string. So you have to return the result from this and capture the promise by calling the then function once again and pass in the function that will be called once the promise resolves.

The JSON string would then be passed as an argument to this function so we just return it. The fetch method returns a promise so when we call the api method, we still have to call the then method to capture the actual response, just like we did in the first part of this series.
  1. api(story_url).then(
  2.     (story) => {
  3.        ...
  4.     }
  5. );
2. WebPageComponent
  The WebPage component is responsible for rendering a web page. It uses the WebView component to do so.
  1. var React = require('react-native');
  2. var {
  3.   AppRegistry,
  4.   StyleSheet,
  5.   Text,
  6.   View,
  7.   WebView
  8. } = React; 
  9. var Button = require('react-native-button');
  10. var GiftedSpinner = require('react-native-gifted-spinner'); 
  11. var _ = require('lodash');
  12.  var WebPage = React.createClass({
  13.     getInitialState: function() {
  14.         return {
  15.             isLoading: true
  16.         };
  17.     }, 
  18.     render: function(){             
  19.         return (<View style={styles.container}>         
  20.             <View style={styles.webview_header}>
  21.               <View style={styles.header_item}>
  22.                 <Button style={styles.button} onPress={this.back}>Back</Button>
  23.               </View>
  24.               <View style={styles.header_item}>
  25.                 <Text style={styles.page_title}>{this.truncate(this.state.pageTitle)}</Text>
  26.               </View>
  27.               <View style={[styles.header_item, styles.spinner]}>
  28.                 { this.state.isLoading && <GiftedSpinner /> }
  29.               </View>
  30.             </View>
  31.             <View style={styles.webview_body}>
  32.                 <WebView 
  33.                     url={this.props.url}
  34.                     onNavigationStateChange={this.onNavigationStateChange}                     
  35.                 />
  36.             </View>
  37.         </View>); 
  38.     }, 
  39.     truncate: function(str){
  40.         return _.truncate(str, 20);
  41.     }, 
  42.     onNavigationStateChange: function(navState) {         
  43.         if(!navState.loading){
  44.             this.setState({
  45.                 isLoading: false,
  46.                 pageTitle: navState.title
  47.             });
  48.         }
  49.     },     
  50.     back: function(){
  51.        this.props.navigator.pop();
  52.     }
  53. });  
  54. var styles = StyleSheet.create({
  55.     container: {
  56.         flex: 1
  57.     },
  58.     webview_header: {
  59.         paddingLeft: 10,
  60.         backgroundColor: '#FF6600',
  61.         flex: 1,
  62.         justifyContent: 'space-between',
  63.         flexDirection: 'row'
  64.     },
  65.     header_item: {
  66.         paddingLeft: 10,
  67.         paddingRight: 10,
  68.         justifyContent: 'center'
  69.     },
  70.     webview_body: {
  71.         flex: 9
  72.     },
  73.     button: {
  74.         textAlign: 'left',
  75.         color: '#FFF'
  76.     },
  77.     page_title: {
  78.         color: '#FFF'
  79.     },
  80.     spinner: { 
  81.         alignItems: 'flex-end'
  82.     }
  83. }); 
  84. module.exports = WebPage;
First, we do some housekeeping by creating the variables we need and requiring the libraries we'll be using.
  1. var React = require('react-native');
  2. var {
  3.   AppRegistry,
  4.   StyleSheet,
  5.   Text,
  6.   View,
  7.   WebView
  8. } = React; 
  9. var Button = require('react-native-button');
  10. var GiftedSpinner = require('react-native-gifted-spinner');
  11. var _ = require('lodash');
Next, we create the WebPage component.
  1. var WebPage = React.createClass({
  2.     ...
  3. });
We set isLoading to true as the default state. This property is responsible for determining whether or not to show the spinner. By default, the spinner should be visible to indicate that the page is loading.
  1. getInitialState: function() {
  2.     return {
  3.         isLoading: true
  4.     };
  5. },
Next, we render the component. Like the news item component, this one also has a header and a body. The header contains a back button, the title of the page, and a spinner.
  1. render: function(){         
  2.     return (<View style={styles.container}>     
  3.         <View style={styles.webview_header}>
  4.           <View style={styles.header_item}>
  5.             <Button style={styles.button} onPress={this.back}>Back</Button>
  6.           </View>
  7.           <View style={styles.header_item}>
  8.             <Text style={styles.page_title}>{this.truncate(this.state.pageTitle)}</Text>
  9.           </View>
  10.           <View style={[styles.header_item, styles.spinner]}>
  11.             { this.state.isLoading && <GiftedSpinner /> }
  12.           </View>
  13.         </View> 
  14.         <View style={styles.webview_body}>
  15.             <WebView 
  16.                 url={this.props.url}
  17.                 onNavigationStateChange={this.onNavigationStateChange}
  18.             />
  19.         </View>
  20.     </View>);
  21. },
The body contains the WebView component. The WebView component has a url and onNavigationStateChange attributes. The url is the URL that was passed from the viewPage function in the NewsItems component earlier. So when the following code is executed:
  1. this.props.navigator.push({name: 'web_page', url: url});
The renderScene method in index.android.js also gets executed and the URL is passed to it:
  1. renderScene: function(route, navigator) {
  2.  
  3.     var Component = ROUTES[route.name];
  4.     return (
  5.         <Component route={route} navigator={navigator} url={route.url} />
  6.     );
  7. },
That is how we have access to the URL by extracting it from the props: this.props.url.

Let's go back to the attributes added to the WebView component. We have the onNavigationStateChange attribute, which is used for specifying the function to execute whenever the web view navigates to a new page. This is what that function looks like:
  1. onNavigationStateChange: function(navState) {
  2.      
  3.     if(!navState.loading){
  4.         this.setState({
  5.             isLoading: false,
  6.             pageTitle: navState.title
  7.         });
  8.     }
  9. },
When the above function is called, the navState is passed along as an argument. This contains information about the current state of the web view, such as the title of the page and whether or not it is currently loading. This is the perfect place to update the state. When the page is no longer loading, we set isLoading to false and set a value for the pageTitle.

Next, we have the back function, which makes the navigator go back one page. This gets called whenever the user taps the back button in the header.
  1. back: function(){
  2.    this.props.navigator.pop();
  3. }
The truncate function limits the length of whatever is passed into the function. We use this function to limit the text for the page title of the web page.
  1. truncate: function(str){
  2.     return _.truncate(str, 20);
  3. },
The stylesheet looks like this:
  1. var styles = StyleSheet.create({
  2.     container: {
  3.         flex: 1
  4.     },
  5.     webview_header: {
  6.         paddingLeft: 10,
  7.         backgroundColor: '#FF6600',
  8.         flex: 1,
  9.         justifyContent: 'space-between',
  10.         flexDirection: 'row'
  11.     },
  12.     header_item: {
  13.         paddingLeft: 10,
  14.         paddingRight: 10,
  15.         justifyContent: 'center'
  16.     },
  17.     webview_body: {
  18.         flex: 9
  19.     },
  20.     button: {
  21.         textAlign: 'left',
  22.         color: '#FFF'
  23.     },
  24.     page_title: {
  25.         color: '#FFF'
  26.     },
  27.     spinner: {
  28.         alignItems: 'flex-end'
  29.     }
  30. });
Lastly, expose the component to the outside world:
  1. module.exports = WebPage;
Written by Wernher-Bel Ancheta
If you found this post interesting, follow and support us.
Suggest for you:



React.js - A guide for Rails developers_part 3 (end)


setState/replaceState: Deleting Records


The next feature in our list is the ability to delete records, we need a new Actions column in our records table, this column will have a Delete button for each record, pretty standard UI. As in our previous example, we need to create the destroy method in our Rails controller:
  1.  # app/controllers/records_controller.rb
  2.   class RecordsController < ApplicationController
  3.     ...
  4.     def destroy
  5.       @record = Record.find(params[:id])
  6.       @record.destroy
  7.       head :no_content
  8.     end
  9.     ...
  10.   end
That is all the server-side code we will need for this feature. Now, open your Records React component and add the Actions column at the rightmost position of the table header:
  1.   # app/assets/javascripts/components/records.js.coffee
  2.   @Records = React.createClass
  3.     ...
  4.     render: ->
  5.       ...
  6.       # almost at the bottom of the render method
  7.       React.DOM.table
  8.         React.DOM.thead null,
  9.           React.DOM.tr null,
  10.             React.DOM.th null, 'Date'
  11.             React.DOM.th null, 'Title'
  12.             React.DOM.th null, 'Amount'
  13.             React.DOM.th null, 'Actions'
  14.         React.DOM.tbody null,
  15.           for record in @state.records
  16.             React.createElement Record, key: record.id, record: record
And finally, open the Record component and add an extra column with a Delete link:
  1.   # app/assets/javascripts/components/record.js.coffee
  2.   @Record = React.createClass
  3.     render: ->
  4.       React.DOM.tr null,
  5.         React.DOM.td null, @props.record.date
  6.         React.DOM.td null, @props.record.title
  7.         React.DOM.td null, amountFormat(@props.record.amount)
  8.         React.DOM.td null,
  9.           React.DOM.a
  10.             className: 'btn btn-danger'
  11.             'Delete'
Save your file, refresh your browser and... We have a useless button with no events attached to it!

Let's add some functionality to it. As we learned from our RecordForm component, the way to go here is:
  1. Detect an event inside the child Record component (onClick)
  2. Perform an action (send a DELETE request to the server in this case)
  3. Notify the parent Records component about this action (sending/receiving a handler method through props)
  4. Update the Record component's state
To implement step 1, we can add a handler for onClick to Record the same way we added a handler for onSubmit to RecordForm to create new records. Fortunately for us, React implements most of the common browser events in a normalized way, so we don't have to worry about cross-browser compatibility (you can take a look at the complete events list here).
Re-open the Record component, add a new handleDelete method and an onClick attribute to our "useless" delete button as follows:
  1.  # app/assets/javascripts/components/record.js.coffee
  2.   @Record = React.createClass
  3.     handleDelete: (e) ->
  4.       e.preventDefault()
  5.       # yeah... jQuery doesn't have a $.delete shortcut method
  6.       $.ajax
  7.         method: 'DELETE'
  8.         url: "/records/#{ @props.record.id }"
  9.         dataType: 'JSON'
  10.         success: () =>
  11.           @props.handleDeleteRecord @props.record
  12.     render: ->
  13.       React.DOM.tr null,
  14.         React.DOM.td null, @props.record.date
  15.         React.DOM.td null, @props.record.title
  16.         React.DOM.td null, amountFormat(@props.record.amount)
  17.         React.DOM.td null,
  18.           React.DOM.a
  19.             className: 'btn btn-danger'
  20.             onClick: @handleDelete
  21.             'Delete'
When the delete button gets clicked, handleDelete sends an AJAX request to the server to delete the record in the backend and, after this, it notifies the parent component about this action through the handleDeleteRecord handler available through props, this means we need to adjust the creation of Record elements in the parent component to include the extra property handleDeleteRecord, and also implement the actual handler method in the parent:
  1.  # app/assets/javascripts/components/records.js.coffee
  2.   @Records = React.createClass
  3.     ...
  4.     deleteRecord: (record) ->
  5.       records = @state.records.slice()
  6.       index = records.indexOf record
  7.       records.splice index, 1
  8.       @replaceState records: records
  9.     render: ->
  10.       ...
  11.       # almost at the bottom of the render method
  12.       React.DOM.table
  13.         React.DOM.thead null,
  14.           React.DOM.tr null,
  15.             React.DOM.th null, 'Date'
  16.             React.DOM.th null, 'Title'
  17.             React.DOM.th null, 'Amount'
  18.             React.DOM.th null, 'Actions'
  19.         React.DOM.tbody null,
  20.           for record in @state.records
  21.             React.createElement Record, key: record.id, record: record, handleDeleteRecord: @deleteRecord
Basically, our deleteRecord method copies the current component's records state, performs an index search of the record to be deleted, splices it from the array and updates the component's state, pretty standard JavaScript operations.
We introduced a new way of interacting with the state, replaceState; the main difference between setState and rreplaceState is that the first one will only update one key of the state object, the second one will completely override the current state of the component with whatever new object we send.
After updating this last bit of code, refresh your browser window and try to delete a record, a couple of things should happen:

  1. The records should disappear from the table and...
  2. The indicators should update the amounts instantly, no additional code is required


We are almost done with our application, but before implementing our last feature, we can apply a small refactor and, at the same time, introduce a new React feature.
You can take a look at the resulting code of this section here, or just the changes introduced by this section here.

Refactor: State Helpers

At this point, we have a couple of methods where the state gets updated without any difficulty as our data is not what you might call "complex", but imagine a more complex application with a multi-level JSON state, you can picture yourself performing deep copies and juggling with your state data. React includes some fancy state helpers to help you with some of the heavy lifting, no matter how deep your state is, these helpers will let you manipulate it with more freedom using a kind-of MongoDB's query language (or at least that's what React's documentation says).

Before using these helpers, first we need to configure our Rails application to include them. Open your project's config/application.rb file and add config.react.addons = true at the bottom of the Application block:
  1.   # config/application.rb
  2.   ...
  3.   module Accounts
  4.     class Application < Rails::Application
  5.       ...
  6.       config.react.addons = true
  7.     end
  8.   end
For the changes to take effect, restart your rails server, I repeat, restart your rails server. Now we have access to the state helpers through React.addons.update, which will process our state object (or any other object we send to it) and apply the provided commands. The two commands we will be using are $push and $splice (I'm borrowing the explanation of these commands from the official React documentation):
  • {$push: array} push() all the items in array on the target.
  • {$splice: array of arrays} for each item in arrays call splice() on the target with the parameters provided by the item.
We're about to simplify addRecord and deleteRecord from the Record component using these helpers, as follows:
  1.   # app/assets/javascripts/components/records.js.coffee
  2.   @Records = React.createClass
  3.     ...
  4.     addRecord: (record) ->
  5.       records = React.addons.update(@state.records, { $push: [record] })
  6.       @setState records: records
  7.     deleteRecord: (record) ->
  8.       index = @state.records.indexOf record
  9.       records = React.addons.update(@state.records, { $splice: [[index, 1]] })
  10.       @replaceState records: records
Shorter, more elegant and with the same results, feel free to reload your browser and ensure nothing got broken.
You can take a look at the resulting code of this section here, or just the changes introduced by this section here.

Reactive Data Flow: Editing Records

For the final feature, we are adding an extra Edit button, next to each Delete button in our records table. When this Edit button gets clicked, it will toggle the entire row from a read-only state (wink wink) to an editable state, revealing an inline form where the user can update the record's content. After submitting the updated content or canceling the action, the record's row will return to its original read-only state.

As you might have guessed from the previous paragraph, we need to handle mutable data to toggle each record's state inside of our Record component. This is a use case of what React calls reactive data flow. Let's add an edit flag and a handleToggle method to record.js.coffee:
  1.   # app/assets/javascripts/components/record.js.coffee
  2.   @Record = React.createClass
  3.     getInitialState: ->
  4.       edit: false
  5.     handleToggle: (e) ->
  6.       e.preventDefault()
  7.       @setState edit: !@state.edit
  8.     ...
The edit flag will default to false, and handleToggle will change edit from false to true and vice versa, we just need to trigger handleToggle from a user onClick event.

Now, we need to handle two row versions (read-only and form) and display them conditionally depending on edit. Luckily for us, as long as our render method returns a React element, we are free to perform any actions in it; we can define a couple of helper methods rrecordRow and recordForm and call them conditionally inside of render depending on the contents of @state.edit.
We already have an initial version of recordRow, it's our current render method. Let's move the contents of render to our brand new recordRow method and add some additional code to it:
  1.   # app/assets/javascripts/components/record.js.coffee
  2.   @Record = React.createClass
  3.     ...
  4.     recordRow: ->
  5.       React.DOM.tr null,
  6.         React.DOM.td null, @props.record.date
  7.         React.DOM.td null, @props.record.title
  8.         React.DOM.td null, amountFormat(@props.record.amount)
  9.         React.DOM.td null,
  10.           React.DOM.a
  11.             className: 'btn btn-default'
  12.             onClick: @handleToggle
  13.             'Edit'
  14.           React.DOM.a
  15.             className: 'btn btn-danger'
  16.             onClick: @handleDelete
  17.             'Delete'
  18.     ...
We only added an additional React.DOM.a element which listens to onClick events to call handleToggle.

Moving forward, the implementation of recordForm will follow a similar structure, but with input fields in each cell. We are going to use a new ref attribute for our inputs to make them accessible; as this component doesn't handle a state, this new attribute will let our component read the data provided by the user through @refs:
  1.  # app/assets/javascripts/components/record.js.coffee
  2.   @Record = React.createClass
  3.     ...
  4.     recordForm: ->
  5.       React.DOM.tr null,
  6.         React.DOM.td null,
  7.           React.DOM.input
  8.             className: 'form-control'
  9.             type: 'text'
  10.             defaultValue: @props.record.date
  11.             ref: 'date'
  12.         React.DOM.td null,
  13.           React.DOM.input
  14.             className: 'form-control'
  15.             type: 'text'
  16.             defaultValue: @props.record.title
  17.             ref: 'title'
  18.         React.DOM.td null,
  19.           React.DOM.input
  20.             className: 'form-control'
  21.             type: 'number'
  22.             defaultValue: @props.record.amount
  23.             ref: 'amount'
  24.         React.DOM.td null,
  25.           React.DOM.a
  26.             className: 'btn btn-default'
  27.             onClick: @handleEdit
  28.             'Update'
  29.           React.DOM.a
  30.             className: 'btn btn-danger'
  31.             onClick: @handleToggle
  32.             'Cancel'
  33.     ...
Do not be afraid, this method might look big, but it is just our HAML-like syntax. Notice we are calling @handleEdit when the user clicks on the Update button, we are about to use a similar flow as the one implemented to delete records.

Do you notice something different on how React.DOM.input are being created? We are using defaultValue instead of value to set the initial input values, this is because using jusvalue without onChange will end up creating read-only inputs.

Finally, the render method boils down to the following code:
  1.   # app/assets/javascripts/components/record.js.coffee
  2.   @Record = React.createClass
  3.     ...
  4.     render: ->
  5.       if @state.edit
  6.         @recordForm()
  7.       else
  8.         @recordRow()
You can refresh your browser to play around with the new toggle behavior, but don't submit any changes yet as we haven't implemented the actual update functionality.


To handle record updates, we need to add the update method to our Rails controller:
  1.   # app/controllers/records_controller.rb
  2.   class RecordsController < ApplicationController
  3.     ...
  4.     def update
  5.       @record = Record.find(params[:id])
  6.       if @record.update(record_params)
  7.         render json: @record
  8.       else
  9.         render json: @record.errors, status: :unprocessable_entity
  10.       end
  11.     end
  12.     ...
  13.   end
Back to our Record component, we need to implement the handleEdit method which will send an AJAX request to the server with the updated record information, then it will notify the parent component by sending the updated version of the record via the handleEditRecord method, this method will be received through @props, the same way we did it before when deleting records:
  1.  # app/assets/javascripts/components/records.js.coffee
  2.   @Records = React.createClass
  3.     ...
  4.     updateRecord: (record, data) ->
  5.       index = @state.records.indexOf record
  6.       records = React.addons.update(@state.records, { $splice: [[index, 1, data]] })
  7.       @replaceState records: records
  8.     ...
  9.     render: ->
  10.       ...
  11.       # almost at the bottom of the render method
  12.       React.DOM.table
  13.         React.DOM.thead null,
  14.           React.DOM.tr null,
  15.             React.DOM.th null, 'Date'
  16.             React.DOM.th null, 'Title'
  17.             React.DOM.th null, 'Amount'
  18.             React.DOM.th null, 'Actions'
  19.         React.DOM.tbody null,
  20.           for record in @state.records
  21.             React.createElement Record, key: record.id, record: record, handleDeleteRecord: @deleteRecord, handleEditRecord: @updateRecord
As we have learned on the previous section, using React.addons.update to change our state might result on more concrete methods. The final link between Records and Records is the method @updateRecord sent through the handleEditRecord property.
Refresh your browser for the last time and try updating some existing records, notice how the amount boxes at the top of the page keep track of every record you change.


We are done! Smile, we have just built a small Rails + React application from scratch!
You can take a look at the resulting code of this section here, or just the changes introduced by this section here.

Closing thoughts: React.js Simplicity & Flexibility

We have examined some of React's functionalities and we learned that it barely introduces new concepts. I have heard comments of people saying X or Y JavaScript framework has a steep learning curve because of all the new concepts introduced, this is not React's case; it implements core JavaScript concepts such as event handlers and bindings, making it easy to adopt and learn. Again, one of its strengths is its simplicity.

We also learned by the example how to integrate it into the Rails' assets pipeline and how well it plays along with CoffeeScript, jQuery, Turbolinks, and the rest of the Rails' orchestra. But this is not the only way of achieving the desired results. For example, if you don't use Turbolinks (hence, you don't need react_ujs) you can use Rails Assets instead of the react-rails gem, you could use Jbuilder to build more complex JSON responses instead of rendering JSON objects, and so on; you would still be able to get the same wonderful results.

React will definitely boost your frontend abilities, making it a great library to have under your Rails' toobelt.
Written by Fernando Villalobos

If you found this post interesting, follow and support us.
Suggest for you: