Adding React.js to old Django site

Dec. 11, 2019, 12:34 p.m.

This is a repost of my old story about adding react to an already existing website based on Django framework. While some approaches could be outdated I still find it quite useful for old projects.

Long story short. There is an old sandbox project which was made by me in order to learn how to program in Python Django. To make it a more real-life project rather than a bunch of experiments with another one to-do list or voting app I decided to build it around cycling idea. I love cycling and why not train my programming skills on something I love? :)

So here it is — Pokatuha. (“pokatuha” means ride). The idea is very simple — you can create a route in your city and you can plan a ride so anyone can join it. Nothing fancy but it’s quite interesting to make because you need to have geo-location service, routing service in order to plan rides, photo sharing, etc.

I’m planning to add React.js frontend for a private part of my old Django site pokatuha.com. The path is full of pain, struggle, and monsters. I’m inviting you to join my trip :)

It uses graphhopper routing service, ip geolocation, mapbox mapping service, and some elasticsearch. So you need hell a lot of stuff to glue in all together. And so I used a lot of ducktapes, glue, and coffee to make it something :) All dirty tricks with jQuery you can think about were used here. Currently, it’s a mess of jQuery, pure javascript and Django templates mixed together and very hard to maintain. In order to make it something more reliable and maintainable, I decided to move frontend part to React.js (I was thinking about Vue.js too, but I need to make some React skills so I could make mobile app later with React Native).

It is my process of learning React.js in front of other people. I know there will be a hell lot of mistakes but it’s much easier to progress when you have your thoughts structured somewhere and when there are a lot of good people ready to help :)


Day 1 - some planning

Current situation

  1. There is a public and private part of the site using the same templates which are checking is user authorized or not. There is no big difference between the public and private parts. I want to make the private part more smooth and easy to use. React.js is good for it.
  2. There are public pages that already gained some SEO weight and have some popularity so all the content should be available for current URLs.
  3. There are parts of the site that do not need to be very reactive (blog part for example).
  4. Authorized user browsing react.js rendered pages should see the same URLs as not authorized so she can copy URL and send it via skype or whatever messenger or share it on a social network.

Things to decide

  1. How to separate server-rendered parts and client-rendered. I see no way to put index.html with react app on Nginx and render everything on the client. There is some traffic already coming and I don’t want to lose it.
  2. Check if the user is authorized and provide different templates.
  3. Try to use custom middleware just after authorization middleware so if a user is authorized to use the static file as a template and remove everything down the road of Django request-response cycle

If you have any suggestions feel free to comment and share your thoughts :)


Day 2 - testing middleware

Question of the day: where to plug React in?

So yesterday I decided to make my sandbox site a bit more reactive using React.js. Great but what’s next? There should be the first step to do. But before making this step I’d like to make a brief description of technologies used within a project so it will be easier to make a decision:

Current build process

The biggest issue with Django and webpack is correct static files updating after build. I work alone so there is no need to do server side building — I do everything on my local machine and push it to server.

I store all .js, .scss, icons and fonts files (resources for bundling) inside /assets category of project. I make all javascript changes and style changes here. Django has no idea what’s going on here.

Webpack is building one main.js and one styles.css currently. It takes input form /assets, builds everything and puts output to /bundles category.

Django’s staticfiles is aware of /bundles category and uses it to provide static files for templates.

STATICFILES_DIRS = (
    os.path.join(PROJECT_ROOT, 'bundles'),
)

So during the development process when DEBUG=True static files are provided directly from /bundles. No problems, mankind no need to worry :) Problems come on the production server because of caching. I do not use hashes in names of bundled files so every time I push changes to server users get old content of main.js and styles.css. Nginx is reading static content from /static category. Bundled files are pushed there by manage command collectstatic.

In order to overcome the caching problem I use django-compressor. Probably not the best solution but it works. So every time after pushing code to the server I need to do collectstatic and then compress.

So there are three categories to work with inside project:

I could remove bundles from git tracking and make a build process on a server but currently, I see no need to do it.

I could also drop django-compressor our of building process but I still have some javascript on public part of the site I don’t want to plug into webpack because of namespaces and global variables. They do work, I won’t touch them.

How React scripts will be attached?

Because of my quite weird stack, I have not much choice of how to build and attach my react scripts.

React source code files will be added to /assets category. Probably it will be inside /assests/frontend with components split and views split in separate folders. They’ll be taken by webpack and built into app.js or something like that. App.js will be added to a static html file (just like it should be).

Nginx static HTML file? Nope

It could be awesome if I could provide the user with a static html file if she’s logged in. But I can’t. Nginx is not aware of Django’s authentication system. So nope, Nginx won’t help here :(

Probably, you have ideas?

Django middleware? Well yes

So there is no way to get rid of Django right now. I need to break the request-response cycle as early as possible. The best way to do it, probably — in middlewares. Maybe you know a better way — please share with me :)

Django 1.11 has a beautiful middleware approach to work with. It’s much better than 1.9 version. It took half an hour to update Django from 1.9 to 1.11 with fixing all errors.

Here is my custom middleware:

from django.http import HttpResponse
from django.shortcuts import render
class ClientFrontendMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response
def __call__(self, request):
     if request.user.is_authenticated():
      return render(request,
       "frontend/index.html")
        response = self.get_response(request)
return response

As you can see I intercept request and check for authentication. If user is authenticated she receives rendered template and response cycle is stopped. No more middlewares are called. No views, context processors, etc. I’m using template rendering here because of need to use django-compressor. I see no other way to take care of care of caching now. Here is template:

<!DOCTYPE html>
{% load static compress %}
<html>
  <head>
    <meta charset="utf-8">
    <title>Become reactive as hell</title>
  </head>
<body>
    <h1>Welcome to the jungle</h1>
    <div id="app"></div>
    {% compress js %}
       <script src="{% static'/frontend/js/app.js" />
    {% endcompress %}
  </body>
</html>

And here is were i put it in settings file. Just after authentication middleware because I need to know if user authenticated:

MIDDLEWARE = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',

    'pokatuha.middleware.ClientFrontendMiddleware', # our middleware

    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.contrib.redirects.middleware.RedirectFallbackMiddleware',
    'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'geolocation.middleware.GeolocationMiddleware',
)

Let’s make runserver and log in. Here we are:

Response from server using our custom middleware

It’s alive and it’s working.

What’s next?

I’ll stick to current way of doing things. Cons:

Well the last one could be not so bad. I’m planning to use django rest framework to provide state for my React app. And there is possibility to try server-side rendering plugin for React. Then Django will stay only for backend stuff and admin.


Day 3 - fast decisions are not always effective

Last day I was happy sticking authorization check in middleware just after authorization middleware and seeing it worked. But I was too optimistic.

After visiting /admin part of site I found the same picture as on mainpage:

Response from server using our custom middleware

It means that middleware doesn’t care what URL I’m typing in. And it’s the absolutely right thing to do because all the URL stuff comes after all middlewares. There are a few options:

  1. Move authorization down the road to view layer and show templates with react only for specific views. Probably use Class-based views with inherited from base view with authorization check.
  2. Add URL checking to middleware.

Although the first way seems like a good idea for further maintainability it would cause rewriting all views because I’m not using Class-Based Views. I could also use a decorator instead of an inherited CBV.

The second one makes everything work right now. I’ll stick to it.

Nevertheless, both ways seem like sh*ty to me. Both of them are good places for errors to populate like a dirty kitchen for cockroaches. If only there was a better way… (looking at node.js react server-side rendering).

So I’ll check for request.path in middleware and if it starts with something important to me (like “/admin”) no request/response cycle would be broken. There is a good method called startswidth for any string which could check it’s string against any string or tuple of strings and return true if at least one found.

Let’s add some URLs to settings.py file:

ONLY_BACKEND_URLS = (
    '/admin',
    '/ckeditor',
    '/select2',
)

And our middleware will become:

class ClientFrontendMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response
def __call__(self, request):
     if request.user.is_authenticated() and not request.path.startswith(settings.ONLY_BACKEND_URLS):
      return render(request,
       "frontend/index.html")
        response = self.get_response(request)
return response

Now after adding Django rest framework I’ll need to add it’s URL to ONLY_BACKEND_URLS in settings.

Now everything works. I really don’t like this way of doing things but still, don’t have a better idea. It adds one more layer of complexity and is bug-prone.

Day 4 - React (finally)

Previous 3 days were sacrificed to the gods of preparation. Today I gonna get bless from them and finally plug some React code into my old project.

The good news is: I won’t bother Django code for the next few days (except one template today) before adding DRF for API. I’ll focus mainly on client-side code.

Bad news is: there are preparation gods in React land too. And they need some sacrifices too. I’m talking about npm, Webpack and project structure.

Project structure

In order to keep serverside stuff and clientside separated, I need to make some changes for the assets folder which is used as a source for my webpack.

First of all, I’ll move all my old code inside /assets/serverside. I’ll also need to change webpack configuration later to keep it working with old files.

Secondly, I need to create a folder for my new shiny client app: /assets/clientside.

Now all my work will be done inside /assets/clientside.

There are different approaches on how to structure React.js project. I have no experience with React at all but understand that my app is quite complicated and feature-rich so I need to use a strict structure. I really like what this guy is proposing.

So here is my structure of /assets category:

serverside/
  some old stuff here
clientside/
  components/
  data/
  scenes/
  services/
  App.js
  index.js

/assets/clientside/index.js will be used as an entry point for webpack.

Webpack and npm

I already have webpack in my project. It is used for bundling all useful jquery stuff in one single file main.js. Also, there is a sass pre-processor, text-extraction plugin for making .css files, fonts and images extraction, etc.

Before making any changes I decided to update Webpack to 2.2.0 from my 1.x.x version. It was quite a pain in the ass but worth it. Just remember you need to use full names of loaders (‘sass-loader’ instead of ‘sass’, ‘file-loader’ instead of a file, etc.) Also, some packages for node.js should be updated during this process (sass didn’t work).

In order to keep old javascript code and to add new code for bundling I’m using multiple entry points in Webpack config inside module.exports part:

entry: {
        main: './assets/serverside/js/index',
        client: './assets/clientside/index'
    },
output: {
        path: path.resolve('./bundles/'),
        filename: 'js/[name].js', 
    },

After build process I’ll get two js files inside my bundles folder (which is used for collectstatic inside Django): main.js and client.js.

Main.js is old sh*t used for classic serverside rendering of Django. I won’t bother it anymore.

Client.js is new one used for mounting React app.

I also installed dependencies needed for successful react build process in npm:

npm install babel babel-core babel-loader babel-preset-es2015 babel-react-preset react react-dom

Here is how modules looks inside module.exports of webpack configuration file:

module: {
        loaders: [
            {test: /\.js$/,
              exclude: /node_modules/,
              loader: 'babel-loader',
              query: {
                presets: ['es2015', 'react']
              }
            },
            {test: /\.scss$/,
              loader: ExtractTextPlugin.extract('css-loader!sass-loader')
            },
            {
                test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
                loader: 'file-loader?name=fonts/[name].[ext]&publicPath=../'
            },
            {
                test: /\.(jpe?g|png|gif|svg)$/i,
                loaders: [
                    'file-loader?name=img/[name].[ext]&publicPath=../'
                ]
            },
        ]
    },

React finally

It’s time to turn some React.js on! :)

I won’t add any functionality here because the only goal for today is to make all boilerplate dirty job and make React show at least something.

Let’s add some code to /assets/clientside/index.js:

import React, { Component } from 'react';
import ReactDom from 'react-dom';
import App from './App';
ReactDom.render(
 <App />,
 document.getElementById('app')
);

As you can see I’re importing App.js here. So let’s add App.js into /assets/clientside/:

import React, { Component } from 'react';
class App extends Component {
 render() {
  return (
   <div>
    <h1>It's alive</h1>
    <p>Welcome to pokatuha. Let's ride together!</p>
   </div>
  );
 }
};
export default App;

And finally I need to make one small change to Django template which is rendered by my custom middleware:

<!DOCTYPE html>
{% load static compress %}
<html>
  <head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="{% static "images/favicon.ico" %}" type="image/ico">
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <title>Pokatuha.com - let's ride together</title>
  </head>
  <body>
    <div id="app"></div>
    <script type='text/javascript' src="{% static 'js/client.js' %}"></script>
  </body>
</html>

That's all. After running webpack building process all the code is bundled to /bundles/js/client.js which is used by collectstatic of Django. Running Django server and seeing this if the user is authenticated:

Rendered with React

Doesn’t look impressive? Doesn't. But it means a lot! Finally, I can start working with React. Hope you enjoyed this trip.

The next day I’m planning to add React Router and plan some Scenes (views) which will be used by my app.


Day 5 - some routing

The current site is public and already has a URL structure and some SEO weight I want to keep. So I need to be sure that it’s possible to recreate the exact URL structure as on the old part of the site provided by Django.

Two questions to answer today:

  1. Can I connect the react-router without too much pain?
  2. Is it possible to use nested routes in react-router?

Also, as you already could see, I’m trying to make little changes every day (actually it takes much more time to write posts than actual coding but I’d like to improve my English writing skills too :). Nevertheless I need to push code into public site as much earlier as possible to get real feedback from users. Rewriting all the front-end would take up to month with all writing on medium so every day here should bring one small but real live update with some value to users. That’s why one more extra question is:

Can I make minor changes and push them to the public every day?

Connecting react router

I won’t be unique here and will use existing library react-router-dom.

npm install react-router-dom --save-dev

I’ll wrap my App component into Router component inside main index.js file:

import React, { Component } from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom'; 

import App from './App';

ReactDom.render(
	<Router>
		<App />
	</Router>,
	document.getElementById('app')
);

After this change in main index.js file I can use Route component inside main App component of whatever else component used in App.

It’s time to test if router actually works. Let’s modify App.js:

import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';

class App extends Component {

	render() {
		return (
			<div>
			  <main>
				  <Route 
				  	path='/' 
				  	exact 
				  	render={() => (
					  		<h1>It's mainpage</h1>
					  		)
				  	}
				  	
				  />
				  <Route
				  	path='/test/'
				  	render={() => (
					  		<h1>It's test page</h1>
					  		)
				  	}
				  />
			  </main>
			</div>
		)
	};
};

export default App;

There are only two routes checked: “/” for mainpage (with using exact parameter because otherwise, it will take care of all URLs) and ‘/test/’ for the second test page.

After building with webpack and running server I could see some results:

Mainpage rendered with react

Test page rendered with react

Ah! Isn’t it beautiful? (It’s not). So the installed routing library is working and it’s nice. It’s time to test out if it’s possible to use nested routing (I have no idea before writing it).

Nested routing

Django has a nice routing built-in. Most of all I like the idea to place application specific routes inside urls.py file of application and then connect this file inside main urls.py of project. It would be nice if such approach would work for React router. Let’s find out.

I’ll test out only one part of site — the one which is showing bicycle routes inside specific region. There are already exisiting routes in Django I’d like to recreate:

There are few more urls but it’s enough to test these three for now.

In order to follow Django’s approach to urls I need to put all control of nested urls inside nested component. So the new App.js will be:

import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';

import RoutesScene from './scenes/RoutesScene'

class App extends Component {

	render() {
		return (
			<div>
			  <main>
				  <Route 
				  	path='/' 
				  	exact 
				  	render={() => (
				  		<h1>It's mainpage</h1>
				  		)
				  	}		  	
				  />
				  <Route 
				  	path='/velomarshruty/'
				  	component={RoutesScene}
				  />
			  </main>
			</div>
		)
	};
};

export default App;

As you can see I removed test route here and added new component for route ‘/velomarshruty/’. Component is called RoutesScene and is placed inside /assets/clientside/scenes/RoutesScene/index.js according to project structure chosen by me few days ago.

Scene RoutesScene has the role which is quite close to what urls.py inside application in Django is doing. It’s deciding what to do with all urls inside pokatuha.com/velomarshruty/

In react router you can’t just start nested url with ^ sign like in urls.py on Django. You should work with full path on any level of routing. So such code of RoutesScene/index.js won’t work:

import React, { Component } from 'react';
import { Route } from 'react-router-dom';

class RoutesScene extends Component {

	render() {
		return (
			<div>
				<Route 
					path='/'
					exact
					render={() => (
						<h1>It's Index page of bicycle routes</h1>
					)}
				/>
				<Route
					path='/:region-oblast'
					render={() => (
						<h1>Abba</h1>
					)}
				/>
			</div>
		);
	}
};

export default RoutesScene;

It took some time for me to find the right way (actually it’s on page one of react router documentation but who reads it):

import React, { Component } from 'react';
import { Route } from 'react-router-dom';

class RoutesScene extends Component {

	render() {
		return (
			<div>
				<Route 
					path={this.props.match.path}
					exact
					render={() => (
						<h1>It's Index page of bicycle routes</h1>
					)}
				/>
				<Route
					path={`${this.props.match.path}:regionSlug-oblast/`}
					render={() => (
						<h1>
							It's Index page of bicycle routes in region 
						</h1>
					)}
				/>
				<Route
					path={`${this.props.match.path}v-:citySlug/`}
					render={() => (
						<h1>
							It's Index page of bicycle routes in city
						</h1>
					)}
				/>
			</div>
		);
	}
};

export default RoutesScene;

As you can see it’s necessary to take full path info from props of component. There are Routes for index page of bicycle routes, region index page and city index page.

One more thing is to check if I’m getting params from route the right way.

import React, { Component } from 'react';
import { Route } from 'react-router-dom';

class RoutesScene extends Component {

	render() {
		return (
			<div>
				<Route 
					path={this.props.match.path}
					exact
					render={() => (
						<h1>It's Index page of bicycle routes</h1>
					)}
				/>
				<Route
					path={`${this.props.match.path}:regionSlug-oblast/`}
					component={Region}
				/>
				<Route
					path={`${this.props.match.path}v-:citySlug/`}
					component={City}
				/>
			</div>
		);
	}
};

const Region = ({match}) => {
	return (
		<div>
			<h1>
				It's Index page of bicycle routes in region
			</h1>
			<p>Region slug is: {match.params.regionSlug}</p>
		</div>
	);
}

const City = ({match}) => {
	return (
		<div>
			<h1>
				It's Index page of bicycle routes in city
			</h1>
			<p>City slug is: {match.params.citySlug}</p>
		</div>
	);
}

export default RoutesScene;

After build and testing I’m getting nice info:

Routes index page - rendered with React

Routes region page - rendered with React

Routes city page - rendered with React

Working like a charm. React router is not so scary as I thought. Also, it’s absolutely possible to build URL routing almost exactly the same way as it’s done in Django which sounds awesome to me.

So it’s time for one last question.

Minor updates for live site

It’s 5 days passed from the beginning and no code is pushed to live site which is probably bad. It’s good to have users' feedback earlier. Even if it’s just a sandbox project and user feedback not worth it, it’s quite risky to push a big amount of code in one iteration.

How can I make every day commits and see them live?

My current idea is the following. I don’t need to change all front-end in one release. It’s better to focus on one part of the project and make releases around it every day or few days. The composition of blocking middleware and react-router gives me all the possibilities to do it. Quite fun — I wasn’t planning it.

So from today, I’m planning to focus only on bicycle routes part of my project and move it to react. Blog, trips, and everything else will stay purely on Django on the first release.

Day 6 - let's migrate blog!

In order to keep moving forward and not being stuck in the middle of nowhere I decided to change initial plan and start not with bicycle routes part of my site but with blog part. Idea is simple — there are not so much moving parts in blog, it’s less risky because I’m not adding something, I’m just migrating to client-side rendering with React.js for authenticated users. Nevertheless pokatuha.com/blog has not only articles but few more components used across the site: recent routes, recent trips.

Plan for today

  1. Think about general component structure of the site
  2. Decide what NOT to do
  3. Think about component structure for Blog
  4. Add API endpoints with Django Rest Framework
  5. Review middleware for Django because I need very small part of my site be using client-side rendering
  6. Try to make it all work
  7. Deploy on production

General component structure for site

If you visit Pokatuha you’ll see quite standard structure which consists of few big components:

  1. Header with logo and navigation
  2. Search bar with social networks links
  3. Content part
  4. Footer part

Content Components

Header is shown everywhere on site so it could be just a stateless component with links.

Search bar is quite complex because it should make API-calls for list of cities and provide search. Also it won’t be shown on some pages like creation of bicycle route or trip planning page. I decided to keep visibility of search panel in general state of application. Probably it’s not the best idea but I really don’t want to put its logic inside Router component. That’s why its component <SearchPanel /> will be present at the top of components hierarchy.

import React, { Component } from 'react';
import { Route, Switch } from 'react-router-dom';

import RoutesScene from './scenes/RoutesScene';
import BlogScene from './scenes/BlogScene';

import TopMenu from './components/TopMenu';
import SearchPanel from './components/SearchPanel';

class App extends Component {

	state = {
		_showSearchPanel: true,
	}

	onToggleSearchPanel = (visible) => {
		this.setState({
			_showSearchPanel: visible
		})
	}

	render() {
		return (
			<div>
				<TopMenu />
			  <main>
			  	{/* this.state._showSearchPanel &&	<SearchPanel /> */}
			  	<Switch>
					  <Route 
					  	path='/' 
					  	exact 
					  	render={() => (
					  		<div>
					  		  <h1>
					  		  	It's mainpage. Show panel: {this.state._showSearchPanel}
					  		  </h1>
					  		</div>
					  		)
					  	}		  	
					  />
					  <Route 
					  	path='/velomarshruty/'
					  	render={(props) => (
					  		<RoutesScene
					  			{...props}
					  			onToggleSearchPanel={this.onToggleSearchPanel}
					  		/>
					  	)}
					  />
					  <Route
					  	path='/blog/'
					  	component={BlogScene}
					  />
					  <Route
					  	render={()=> (
					  		<h1>Nothing found</h1>
					  	)}
					  />
				  </Switch>
			  </main>
			</div>
		)
	};
};

export default App;

As you can see visibility of SearchPanel component could be changed by onToggleSearchPanel function which will be passed to some components of app as prop.

Content part is fully dependent on scene components (you can call them views).

Footer is also present on all pages except few. That’s why it’s also kept on top of hierarchy and it’s visibility depends on state of application (I’ll hide footer on some pages)

What I won’t do today

I won’t touch Search panel today. It’s quite complex and will delay deployment to production.

I won’t add Footer. It will take time but won’t provide any value right now.

I won’t add Recent Trips and Recent Routes right now. It will be scope of next release.

Component structure of Blog

There are three scenes here:

API endpoints

It’s up to you how you want to make your server to provide API endpoints. I’m using the full power of Django Rest Framework. Right now there are only three endpoints:

It’s quite obvious what every endpoint provide. The last one is for ArticlePage scene to get extra 4 articles. Because of asynchronous flow of fetch requests user experience shouldn’t be bad because of extra request. =)

Middleware refactoring

My restrictive middleware approach was working okay but here come problems:

  1. If new feature will appear on server-side rendered part of side for not logged in users, logged users will have errors.
  2. Middleware checks for start of url path so I can’t restrict mainpage. I’m not planning changing Mainpage right now because it will be the most complex part of site later and the only reason I decided to use React.

That’s why I decided to change restrictive approach to permitting.

The new look of middleware.py file:

import re

from django.http import HttpResponse
from django.shortcuts import render
from django.conf import settings


class ClientFrontendMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
    	combined = '(?:%s)' % '|'.join(settings.ALLOW_FRONTEND_URLS)
    	if request.user.is_authenticated() and re.match(combined, request.path):
    		return render(request,
    			"frontend/index.html")
    	response = self.get_response(request)

    	return response

As you can see it allows me to use the same approach as in urls.py file.

So in order to make it working I need to add this to settings.py:

ALLOW_FRONTEND_URLS = (
    '^/blog',
)

Now only the part beginning with ‘/blog’ will use client rendering if user is logged in.

Let’s make it work

Let’s start with main scene of Blog — BlogScene. It’s only goal is to provide further routing:

import React, { Component } from 'react';
import { Route, Switch } from 'react-router-dom';

import IndexScene from './scenes/IndexScene';
import ArticlePage from './scenes/IndexScene/scenes/ArticlePage';

class BlogScene extends Component {

    render() {
        return (
            <div>
                <Switch>
                  <Route 
                        path={this.props.match.path}
                        exact
                        component={IndexScene}
                    />
                    <Route
                        path={`${this.props.match.path}cat/:categorySlug/`}
                        component={IndexScene}
                    />
                    <Route
                        path={`${this.props.match.path}:articleSlug/`}
                        component={ArticlePage}
                    />
                    <Route
                        render={() => (
                            <h1>Not found</h1>
                        )}
                    />
                </Switch>
            </div>
        );
    }
};

export default BlogScene;

First Route component is for main page of Blog. It will call IndexScene without additional params.

Second one is for category of blog. It will pass one extra paramater: categorySlug.

Third one is for article page. It will just call the ArticlePage scene and provide one extra parameter in props: articleSlug.

One last is for case if nothing meaningful found.

Switch component makes us sure that only one of Route components inside it will be used.

Index page of Blog

Blog Index page component structure

Blog index page consist of list with Article components and sidebar with extra navigation.

It became quite big component which is not good from React.js philosophy but it will be easier to break everything in smaller components later.

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import InfiniteScroll from 'react-infinite-scroller';

import { client } from 'AppRoot/data/clientapi';

import ArticleCard from './components/ArticleCard';

class IndexScene extends Component {

	state = {
		fetchedArticles: false,
		fetchedCategories: false,
		category: null,
		articles: [],
		categories: [],
		articlesNextPage: null,
	}

	getCategories = () => {
		client.getCategories()
			.then((categories) => {
				this.setState(
					{ fetchedCategories: true, 
						categories: categories,
					}
				)
			})
	}

	getArticles = (categorySlug) => {
		client.getArticles(categorySlug)
			.then((articles) => {
				this.setState({
					fetchedArticles: true,
					articles: articles,
					articlesNextPage: articles.next
				})
			})
	}

	getMoreArticles = () => {
		client.getMoreObjects(this.state.articlesNextPage)
			.then((articles) => {
				let newArticles = this.state.articles;
				newArticles.results = newArticles.results.concat(articles.results);
				this.setState({
					articles: newArticles,
					articlesNextPage: articles.next
				})
			})
	}

	componentDidMount() {
		const categorySlug = this.props.match.params.categorySlug;
		this.getCategories();
		this.getArticles(categorySlug);
	}

	componentWillUpdate(nextProps) {
		if (this.props.match.params.categorySlug !== nextProps.match.params.categorySlug) {
			const categorySlug = nextProps.match.params.categorySlug;
			this.setState({
				fetchedArticles: false
			});
			this.getArticles(categorySlug);
		}
	}

	renderArticles() {
		if (this.state.fetchedArticles) {
			return (
				this.state.articles.results.map((article, index) => (
					<ArticleCard
						key={ article.id }
						id={ article.id }
						title={ article.title }
						creationDate={ article.created_at }
						slug={ article.slug }
						titleImage={ article.title_image }
						commentsCount={ article.comments_count }
						shortPreview={ article.safe_truncated }
					/>
				))
			)
		}
		return (
				<div className='u-loader u-center' />
		)
	}

	renderCategories() {
		if (this.state.fetchedCategories) {
			return (
				this.state.categories.results.map((category, index) => (
					<Link
						className='u-collection-item'
						key={category.id}
						to={`/blog/cat/${category.slug}`}
					>{category.title}</Link>
				))
			)
		};
		return (
				<div className='u-loader u-center' />
		)
	}



	render() {
		return (
			<section className="u-wrapper u-clearfix">
			  <div className="left-bar">
			  	<h1 className='u-header-big'>
			  		Статьи
			  	</h1>
			  	<InfiniteScroll
			  		pageStart={0}
			  		loadMore={this.getMoreArticles}
			  		hasMore={!!this.state.articlesNextPage}
			  		loader={<div className='u-loader u-center' />}
			  	>
			  		{this.renderArticles()}
			  	</InfiniteScroll>
			  </div>
			  <div className="side-bar">
			  	<h2 className="u-header-big">Разделы</h2>
			  	{this.renderCategories()}
			  	<span className='h-divider' />
			  </div>
			</section>
		);

	}

}

export default IndexScene;

There are few things to think about. First of all is state:

state = {
  fetchedArticles: false,
  fetchedCategories: false,
  category: null,
  articles: [],
  categories: [],
  articlesNextPage: null,
 }

fetchedArticles and fetchedCategories are used to switch between preloader and actual content in renderArticles() and renderCategories() functions.

articles stores array of articles, and categories stores array of categories to show on index page and sidebar menu.

articlesNextPage stores url for next page in articles list when there are extra pages of content (Django Rest Framework returns it as url).

componentWillUpdate is VERY IMPORTANT function here. Because there is no client side filtering of articles by categories (due to heavy pagination use and due to my personal dislike of such approach) when users clicks on categories links state should be updated. But react router doesn’t update state after link click if there is no component change. And in case of blog IndexScene it’s used both for index page of blog and for index page of blog category. In order to update state I have to use this function and check for props.

InfiniteScroll is just external component installed by npm. It triggers getMoreArticles function every time user scrolls to bottom of page and if articlesNextPage in state isn’t falsy (to check if not falsy is use double NOT — !!this.state.articlesNextPage).

In order to show all articles or only for specific category I’m using category slug to filter by (I had to write some filter functions in Django Rest Framework to keep it working)

To render list of articles I’m using stateless component ArticleCard which is mapped to array from state.

import React from 'react';
import { Link } from 'react-router-dom';

const ArticleCard = ({ id, title, creationDate, slug, titleImage, commentsCount, shortPreview }) => {
	return (
		<div>
			<div className="c-article-header u-clearfix">
			  <h2 className="u-header-med u-left">
			  	<Link to={`/blog/${slug}/`}>
			  		{ title }
			  	</Link>
			  </h2>
			  <span className="c-article-header-date u-right">Создано: { creationDate }</span>
			</div>
			<div className="c-article-card u-clearfix">
			  <div className="c-article-card-image">
			    <Link to={`/blog/${slug}/`}>
			  		<img src={titleImage.thumbnail} alt="" title="" />
			  	</Link>
			  </div>
			  <div className="c-article-card-footer">
			    { shortPreview }
			    <div className="c-article-card-links">
			      <Link to={`/blog/${slug}/`}>
			      	Перейти к статье
			      </Link> |
			      Комментариев: { commentsCount }
			    </div>
			  </div>
			</div>
		</div>
	)
};

export default ArticleCard;

Article Page

ArticlePage scene is using the same approach. I’m checking article slug passed down to component by react router and fetch article from server by it’s slug.

import React, { Component } from 'react';
import { Link } from 'react-router-dom';

import { client } from 'AppRoot/data/clientapi';

import ArticleMini from './components/ArticleMini';

class ArticlePage extends Component {

	state = {
		article: null,
		recentArticles: null
	}

	getArticle = (articleSlug) => (
		client.getArticle(articleSlug)
			.then((article) => {
				this.setState({
					article: article
				})
			})
	)

	getRecentArticles = () => {
		client.getRecentArticles()
			.then((articles) => {
				this.setState({
					recentArticles: articles,
				})
			})
	}

	componentDidMount() {
		const articleSlug = this.props.match.params.articleSlug;
		this.getArticle(articleSlug);
		this.getRecentArticles();
	}

	componentWillUpdate(nextProps) {
		if (this.props.match.params.articleSlug !== nextProps.match.params.articleSlug) {
			const articleSlug = nextProps.match.params.articleSlug;
			this.setState({
				article: null
			});
			this.getArticle(articleSlug);
		}
	}

	renderTitleImage() {
		if (!this.state.article.title_image.fullsize.endsWith('placeholder.png')) {
			return(
				<img
					src={ this.state.article.title_image.thumbnail }
					className='c-article-page-title-image'
				/>
			)
		}
		return null
	}

	renderRecentArticles() {
		if (this.state.recentArticles) {
			return(
				this.state.recentArticles.results.map((article) => {
					if (article.slug !== this.state.article.slug ) {
						return(
							<ArticleMini 
								key={ article.id }
								title={ article.title }
								title_image={ article.title_image }
								comments={ article.comments }
								created_at={ article.created_at }
								safe_truncated={ article.safe_truncated }
								slug={ article.slug }
							/>
						);
					};
				})
			)
		} else {
			return(
				<div className='u-loader u-center' />
			)
		}
	}

	createContentHTML = () => {
		return ({
			__html: this.state.article.content
		})
	}

	render() {
		if (this.state.article) {
			return(
				<section className="u-wrapper u-clearfix">
				  <div className="left-bar u-clearfix">
				    <div className="c-article-page-wrapper">
				      <h1 className="u-header-big">{ this.state.article.title }</h1>
				      { this.state.article.created_at }
				      { this.renderTitleImage() }
				      <div dangerouslySetInnerHTML={ this.createContentHTML() } />
				    </div>
				    <span className="h-divider"></span>
				  </div>
				  <div className="side-bar">
				    <h2 className="u-header-big">Другие статьи</h2>
				    { this.renderRecentArticles() }
				  </div>
				</section>
			)
		} else {
			return(
				<div className='u-loader u-center' />
			)
		}
	}
};

export default ArticlePage;

There are only few interesting things.

First of all because ArticleScene is stateful component and because there are links to other articles on Article Scene I have to user componentWillUpdate again. Using it I’m sure that if user clicks on link to another article she’ll get there.

And secondly I have to use dangerouslySetInnerHTML prop of div component in order to show article content with all html markup inside it. Other way all html tags will be escaped and content will become nonsense.

Push to production

Well there are few things I decided to drop off during this day. I won’t give logged users possibility to comment on articles yet (they don’t care anyway =). And I won’t pull recent Trips and Bicycle routes which are present as links on server-side rendered part of site right now because they need extra API work.

Some conclusions:

  1. It’s not fast to make react app at all :( I have to spend 4x-5x more time than on pure Django with some jQuery. Probably it’s because of my little JavaScript knowledge.
  2. Webpack will eat your soul on dinner
  3. Sometimes JavaScript could look like as beautiful language :)

Join me in my trip of making old Django site Reactive with React.js :)

Few edits few hours later

I found interesting bug few hours later. If you click switch between categories and have list of articles out of one pagination page you start to fetch articles from another categories. It happens because of asynchronous way of Fetch in Javascript. Problem hides in componentWillUpdate function:

componentWillUpdate(nextProps) {
  if (this.props.match.params.categorySlug !== nextProps.match.params.categorySlug) {
   const categorySlug = nextProps.match.params.categorySlug;
   this.setState({
    fetchedArticles: false
   });
   this.getArticles(categorySlug);
  }
 }

Because InfiniteScroll component checks articlesNextPage in state before calling it’s own fetching function. In order to keep everything work exactly as I wanted I had to add extra line to componentWillUpdate function:

componentWillUpdate(nextProps) {
  if (this.props.match.params.categorySlug !== nextProps.match.params.categorySlug) {
   const categorySlug = nextProps.match.params.categorySlug;
   this.setState({
    fetchedArticles: false,
    articlesNextPage: null // <-- this will help
   });
   this.getArticles(categorySlug);
  }
 }

Now every time you switch between categories articlesNextPage becomes falsy and InfiniteScroll doesn’t call it’s own fetching function. Asynchronous request are soooo mind-blowing :)

Day 8 - making million bucks with AdSense

As any greedy owner of any sh*tty project I want to think about money first of all and then about UX, UI and U-put-any-letter-here. So let’s make few million dollars with AdSense :)

Because I am SO greedy that wan’t to put AdSense blocks everywhere on site it should be reusable component and any scene should have access to it. That’s why it should be placed somewhere on top of our components and scenes tree:

App.js
components/
  AdBanner/
  SearchPanel/
  TopMenu/
data/
index.js
scenes/
services/

So I’ll have AdBanner React component that I could call from any module in components tree. But how can I use the same path everywhere? To do it I decided to use alias inside Webpack configuration.

resolve: {
        modules: ['node_modules'],
        extensions: ['.js', '.jsx'],
        alias: {
            jquery: "jquery/src/jquery",
            AppRoot: path.resolve('./assets/clientside'),
        }
    }

As you can see there is AppRoot alias added. Now if I use AppRoot somewhere inside import statement of my bundled javascript it will pass change it for absolute path to ./assets/clientside which is top level of my client-side React App. (Actually I already used this alias previous day)

AdSense banner component

Let’s look typical AdSense code for adaptive ads:

<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
        
        <ins class="adsbygoogle"
             style="display:block"
             data-ad-client="ca-pub-XXXXXXXXXXX"
             data-ad-slot="XXXXXXXXXXX"
             data-ad-format="auto"></ins>
        <script>
        (adsbygoogle = window.adsbygoogle || []).push({});
        </script>

I’m not going to vary my client id on different pages so it could stay inside component as static value.

I’m going to change slot property in order to keep some data for analysis in AdSense. So data-ad-slot should be variable and I’m going to push it to component as prop:

import React, { Component } from 'react';

class AdBanner extends Component {

	componentDidMount() {
		(window.adsbygoogle = window.adsbygoogle || []).push({})
	}

	render() {
		const style = {
			display: 'block',
		};

		return(
			<ins className="adsbygoogle"
			     style={style}
			     data-ad-client="ca-pub-XXXXXXXXXXXXX"
			     data-ad-slot={this.props.slot}
			     data-ad-format="auto">
			</ins>
		);
	}
}

export default AdBanner;

BannerAd is stateless component but I’m using class extension in order to have componentDidMount function available.

In order to use AdSense script I have to add it into <head> part of the main template. If you need Google Tag Manager you should also place it in html template used by Django:

<!DOCTYPE html>
{% load static compress %}

<html>
  <head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="{% static "images/favicon.ico" %}" type="image/ico">
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    {% compress css %}
    <link type="text/css" rel="stylesheet" href="{% static 'css/main.css' %}">
    {% endcompress %}
    <title>Pokatuha.com - катаемся на велосипедах вместе!</title>
  </head>
  <body>
    <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
    <div id="app"></div>
    {% compress js %}
    <script type='text/javascript' src="{% static 'js/client.js' %}"></script>
    {% endcompress %}
  </body>
</html>

Adding banners to Blog

Now we can import component in any module of our project by using statement:

import AdBanner from 'AppRoot/components/AdBanner'

Let’s use it on our article page (ArticlePage scene). All I need to do is change render() function of component:

render() {
		if (this.state.article) {
			return(
				<section className="u-wrapper u-clearfix">
				  <div className="left-bar u-clearfix">
				    <div className="c-article-page-wrapper">
				      <h1 className="u-header-big">{ this.state.article.title }</h1>
				      { this.state.article.created_at }
				      { this.renderTitleImage() }
				      <AdBanner slot='3161205038' />
				      <div dangerouslySetInnerHTML={ this.createContentHTML() } />
				      <AdBanner slot='6114671432' />
				    </div>
				    <span className="h-divider"></span>
				  </div>
				  <div className="side-bar">
				    <h2 className="u-header-big">Другие статьи</h2>
				    { this.renderRecentArticles() }
				  </div>
				</section>
			)
		} else {
			return(
				<div className='u-loader u-center' />
			)
		}
	}

That’s all. I’m adding two instances of AdBanner with different slot properties on one page — before and after actual content of article.

Adding banners to blog index page will be a bit trickier. I want banners to be shown at 3 places:

To do it I have to change renderArticles function inside IndexScene scene of Blog:

renderArticles() {
		if (this.state.fetchedArticles) {
			return (
				this.state.articles.results.map((article, index) => (
					<div key={ article.id } >
						{ ( index === 0 || index === 2 || index === 5) && 
							<AdBanner slot="3161205038" />
						}
						<ArticleCard
							key={ article.id }
							id={ article.id }
							title={ article.title }
							creationDate={ article.created_at }
							slug={ article.slug }
							titleImage={ article.title_image }
							commentsCount={ article.comments_count }
							shortPreview={ article.safe_truncated }
						/>
					</div>
				))
			)
		}
		return (
				<div className='u-loader u-center' />
		)
	}

Few things to consider:

Now both logged in and not logged in users of Pokatuha can see advertisements on blog section of site. Now I need only to wait for cash flow in :)

Day 8 - Tag Manager is your best friend

If you’re marketing guy as I am you love to measure a lot of data. You can’t live without opening Google Analytics and looking at visits graph every day. You meditate over behavior flow and content drilldowns every morning with cup of coffee. And if you’re modern and trendy you should use Google Tag Manager for placing Google Analytics code on your site.

I do use Google Tag Manager everywhere on client sides and on my own pet project pokatuha.com. Because I love to have control over how site works and developers should obey me :) But they don’t. They love to talk about task scope, user stories, stage server testing, scale-ability and whatever boring crap. So putting little Google Analytics code on your site could take ages in some cases.

But why am I talking about it on React.js series? Well, if you’re migrating site from classical server-side rendering to client-side, you’re in trouble. By default Google Analytics fires pageview every time you open some page on site or navigate through pages. Problem is that React.js doesn’t fire pageview from Google Analytics point of view.

If you’re following my series of adding React.js to Django site you know that I’ve implemented client-side rendering for Blog section and only for logged in users. So if you’re logged in and come to Blog section Google Analytics will count it as pageview. If you click on side navigation or whatever article link there… Nothing. No new pageviews. Google Analytics thinks that you’re on the same page.

Make Pageviews great again

Good news are that if you’re using Google Tag Manager you’ll need only 1 minute to fix everything. If you’re not shame on you!

React router uses browser history to change urls and make usable navigation. And Google Tag Manager can track it!

  1. Create new trigger in GTM

Creating History Change trigger in GTM

Chose “History Change” type of trigger. Give your trigger name. I’ve called it “History Change” (I’m very original here)

2. Add your new trigger to Google Analytics tag:

Add trigger to Google Analytics tag

3. Submit your changes to public.

Voila! Now everything works as it should. And if you’re migrating site by small parts (as I am) or have mix of server-side and client-side rendering depending on different user cases no need to worry. It already works for both situations without duplication of pageviews.