Creating Awesome SPAs With React
I remember when I started building webapps, I used to rely heavily on the backend framework for most of my templating needs. It felt very tempting(at first) to simply stuff up my pages with Jinja or blade scripts to create a functional user interface. Because back then my understanding of MVC was very limited, this cost me maintainable code.
I came across a very interesting book that discusses the best practices of creating a Single Page Apps , you can find it here. The view layer which I thought of as a mere by-product of my core logic needed much more, its own MVC! The rules are pretty much the same. But it has to be kept in mind that complexity of the model, view, and controller depends on the requirements. For interfaces like GitHub actions per page is comparatively lower than that of Gmail or Canva, managing calls to server is different for apps with low interactivity than those with rich UIs. so it is important to be familiar with the design beforehand, this philosophy favours development in react, because react encourages you to think of your DOM elements as separate components.
Without further ado let’s discuss how the files are arranged in our app.
Constituents
Let’s discuss the different aspects of the SPA and break them down. Lets see what they are first, we’ll wire them up in the next section.
Data Layer
Models in a SPA, like other apps handles the data layer of the project, it is responsible for making API calls that fetch data and uses the JSON response to populate the models, since react don’t have any official way of defining models, I chose to use backboneJS for this. It also spared me the trouble of defining my own datasource.
There are two classes in backboneJS to manage data efficiently, Model and Collection class.
The model
Models are the absolute representation of data in BackboneJS. Following is a snippet of a basic Model.
import Backbone from 'backbone';
const Property = Backbone.Model.extend({
urlRoot : Constants.api_url+'/api/property'
export default Property;
});
This is the code inside Property.js file which is a Backbone model, we can tweak its properties to set up validators, set defaults etc. But most importantly we’ll use it to fetch data from our API. Our model will fetch data with a jqXHR request, all we have to do is specify a valid URL to our API.
Collections
Collections are used to manage sets of models. A model simply represents a single entity of that data type what if your API returns a list of properties?
import Backbone from 'backbone';
import Property from '../models/Property';
var PropertyCollection = Backbone.Collection.extend({
model: Property,
initialize: function(models, options) {
this.data = options.data;
},
url: function(){
return Constants.api_url;
},
parse: function(data) {
return data;
}
});
export default PropertyCollection;
The code above resides in PropertyCollection.js as you can see we initialized the ‘model’ property with Property model we declared earlier. If your REST API follows proper conventions it automatically configures CURD requests, you can simply use .create() .fetch() and .destroy() methods.
View + Controller
For those of you wondering why I mashed up these two together, I have a question. What is the ultimate purpose of a SPA? To deliver the UI! It is just a more complex view layer after all. Having separate controllers would make sense if you are doing some heavy client side calculations. But for most of the apps the only concern is to deliver a seamless interface, and to solve this purpose the views do the heavy lifting! Read this out fo’ your conscious.
In the case of react however eliminating controllers makes even more sense because, because each component has its own rendering and lifecycle methods. Which pretty much covers all the calculations you need.
Before you start writing your views you have to analyse the design of your end-user app. Take a nice look at the designs of the pages youre gonna serve and observe the hierarchy carefully. Look at this example for instance.
Wireframe for a hypothetical APP
Suppose that this is a page for an APP you’re trying to build. At the top level you have Body, which encapsulates everything you want to display. This will be your first react component. Then create the child components one by one in separate file.
import React from 'react';
import Header from '../widgets/Header';
import Title from '../widgets/Title';
import Footer from '../widgets/Footer';
import PropertyDetail from '../widgets/PropertyDetail'
const SinglePage = ({match}) => {
return(
<div id="wrapper" className="clearfix">
<Header></Header>
<Title></Title>
<PropertyDetail property_id={match.params.id}></PropertyDetail>
<Footer></Footer>
</div>
)
}
export default SinglePage;
This is a top level component like Body, of our wireframe from an app I made Header, Title, PropertyDetail, and Footer are all children of top level wrappers. Even PropertyDetail has children of its own an so on.
All the top level components later serve as independent pages of the app
Now lets talk about the wireframe for a bit. Although we know that Body can be the page of our app, but the same cannot be said for Featured Content or Posts , they will instead be the widgets of our app, in most caes the widgets will be making use of the models we discussed earlier.
Routes
Now for routing we’ll use the react router which most of you must have heard of. But before that let’s discuss about the URLs a bit. A URL in a SPA is a denotation of a particular state of the app, i.e. together with the route and parameters the APP can be loaded in a desired state. In a interactive SPA it is very normal to have multiple states per page, you would want accessibility to as many states of your apps through URLs but this would mean messy URL schemes, so its best to balance out both.
To make clean URLs I decided to keep them in a separate file(and not use dynamic URLs 😄) to get a familiar MVC feel. Routes defined using React Router are JSX tags only and components are passed to them as props.
<Router>
<div>
<Route exact path="/search/:keyword" component={ListPage}/>
<Route exact path="/property/:id" component={SinglePage}/>
</div>
</Router>
Here ListPage and SinglePage are my top level react components. Also note that you have to wrap multiple URLs with <div>
tags.
Wiring Up
Okay we know what constitutes an awesome SPA, its time to put the pieces together. Firstly generate a project using create-react-app found in the Facebook incubator repository, it is very helpful in setting up the initial project and all the modern web development tools are managed for you, so you can focus on writing the app rather than configuring webpack(which by the way deserves a separate article to explain). Since you’d be writing modular code, it’d be much easier to write tests, create-react-app uses Jest for running tests.
After generating the app your folder structure will look like this.
my-app/
├── package.json
├── package-lock.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── README.md
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── registerServiceWorker.js
Try running npm start
, if everything’s fine your app should be accessible on 3000 port. Also remember to put all the code in src directory, anything beyond that folder is not accessible, its a restriction imposed by create-react-app generator.
Rearranging stuff
Lets face it, none of those dreams of creating an SPA would ever come true with the current state of working directory. Apart from index.js and registerServiceWorker.js we will move everything to a new directory. before touching anything create this directory structure. Let the scripts stay where they are.
my-app/
├── package.json
├── package-lock.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── js
│ └── manifest.json
├── README.md
└── src
├── assets #All the media/styles/fonts/helper_functions:p
│ ├── objects
│ └── styles
├── components #controller+view Layer
│ ├── pages #top-level pages
│ └── widgets #children
├── data #Data Layer
│ ├── collections #Backbone.Collection
│ └── models #Backbone.Model
├── routes #React router routes
├── App.css #unruly mess
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── registerServiceWorker.js #end unruly mess
AppFlowChart.sucks
First things first create these (not necessarily in this order)
- Create a simple react component in
/src/components/pages
call it Home.js - Create Urls.js in
/src/Urls
and create a route pointing to the Home object<Route exact path=”/” component={Home}/>
. This will render contents of Home.js when you access domain root. - Change the render line in index.js to
ReactDOM.render(<Urls/>, document.getElementById(‘root’));
While doing this make sure each JavaScript file exports only one thing and ensure correct usage of import statement.
Another thing to note here is the top level react pages cannot be react class because React Router passes parameters in urls which are passed as parameters not props!
While it is encouraged to make your widgets as classes that extend React.component. You can now use your models to pass data to your widgets or initialise their state.
Finally when you are ready to deploy the app, run npm run build
, this will bundle all your assets, set react to production mode and save the packed project to build directory of the project. You are now ready to deploy your app!
As a parting note I’d like to say I enjoyed sharing my take on SPAs and it also helped me organise my own thoughts on the topic.