React for Back-End Developers (Part 1)

Published Oct 02, 2017Last updated Dec 17, 2017
React for Back-End Developers (Part 1)

The GitHub repository for this blog post is https://github.com/gbozee/react-for-backend-dev.

React is a front-end web library that can be used to build rich front-end or single page applications (SPA), but that doesn't mean it can't be used alongside an existing back-end web framework.

I would say that this is one of the core strengths of React. With its power, we can spice up our boring back-end web apps/sites with ease.

For the next few blog posts, I'll be discussing some areas where we can use React to augment functionalities provided by the Django Python web framework. This can also be applied to any other server-side web framework.

Our sample scenario consists of implementing a formset in Django. One pain point that Django doesn't solve is the ability to dynamically add a new form to a Django formset on the browser.

One solution to this is using jQuery, but it tends to be difficult to follow. This is a perfect use case to try out React and see how it helps solve this problem. In the scenario we're exploring together, we'll ensure that the following requirements are satisfied:

  1. We can successfully add a new form to the formset.
  2. We can enforce validations imposed by the formset on the server.

To get started, we create a virtual environment to house our web app. I am using git bash for Windows to ensure that the environment is as similar as possible across OS platforms.

$ mkdir react_django
$ cd react_django
$ virtualenv venv
$ source venv/Scripts/activate # in mac and linux source venv/bin/activate
$ pip install django
$ django-admin startproject formset_in_react
$ cd formset_in_react
$ python formset_in_react/manage.py runserver

With the above, we should have a functional Django project up and running.
Let's create our index view that will house our formset in the urls.py provided by Django.
urls.py

I am going to create a single form in Django from which our formset will be based. This form consists of two simple fields, school and degree.

From Django import forms:

class EducationForm(forms.Form):
    school = forms.CharField()
    course = forms.CharField()

EducationFormset = forms.formset_factory(EducationForm,max_num=2)

The formset ensures that the maximum number of forms is no more than two.

Before we venture into React land, let's see the generated HTML from the formset, generated by Django, and hook everything up in our view.

Using the Python interactive shell, we get the following HTML generated from the formset.

$ python formset_in_react/manage.py shell
>>> from formset_in_react.urls import EducationFormset
>>> sample_form = EducationFormset()
>>> sample_form.as_ul()
u'<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS
" /><input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" value="2" id="id_form-MAX_
NUM_FORMS" />\n<li><label for="id_form-0-school">School:</label> <input type="text" name="form-0-school" id="id_form-0-school" /></li>\n<li><label for="id_form-0-cour
se">Course:</label> <input type="text" name="form-0-course" id="id_form-0-course" /></li>`

Prettifying the above output, we get the following:

<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" />

<input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS
" />

<input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS" />

<input type="hidden" name="form-MAX_NUM_FORMS" value="2" id="id_form-MAX_
NUM_FORMS" />

<li>
  <label for="id_form-0-school">School:</label> 
  <input type="text" name="form-0-school" id="id_form-0-school" />
</li>
<li>
<label for="id_form-0-course">Course:</label> 
  <input type="text" name="form-0-course" id="id_form-0-course" />
</li>`

We can see from the output that the generated HTML contains a lot of hidden fields but no button/link to help us create a new form in the formset. We are supposed to use the information in the hidden form fields with a JavaScript library to implement the add functionality.

Let's set up our view and HTML and try to make our form a little bit nicer with Bootstrap.

The complete urls.py content:
urls.PNG

I'll be making a config change to the Django's settings.py so that Django can find our templates location.

settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, "templates")], # the change
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Observe where I created the templates folder:
urls.PNG

The index page should be displaying and accessible in the browser now.

The content of the index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    
    <!-- Optional theme -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <title>Formset with django</title>
    <style>
        .margin-top{
            margin-top: 50px;
        }   
    </style>
</head>
<body>
    <div class="container margin-top">
        <div class="row">
            <div class="col-md-10 col-md-offset-1">
                <form method="post">
                    {% csrf_token %}
                    <div id="formset">
                        {{form.as_ul}}
                    </div>
                </form>
            </div>
        </div>
    </div>
</body>
</html>

We can see here that I created an id before using the default rendering of the formset provided by Django. I'll be hijacking this id with React and replacing it with the modified formset.

We should get the following on our browser:
urls.PNG

Now it's time to set up React. I'll be using the easy approach explained in my last blog post Getting Started in React the Easy Way.

index.html

<head>
...
<script src="https://unpkg.com/react@15.6.0/dist/react.min.js" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/react-dom@15.6.0/dist/react-dom.min.js" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/create-react-class@15.6.0/create-react-class.min.js" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/babel-standalone@6.24.2/babel.min.js"></script>
    <script type="text/babel">
    // Our source code would go here
    </script>
</head>

Remember the original content of the formset we found out about from the terminal. We'll be creating a React component that houses that before making modifications.

// Our source code would go here 
const React = window.React; 
const ReactDOM = window.ReactDOM; 
const createReactClass = window.createReactClass;
const Formset = createReactClass({ 
  render(){ 
    return (
      <div>
       <input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS" />
       <input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" />
       <input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS" />
       <input type="hidden" name="form-MAX_NUM_FORMS" value="2" id="id_form-MAX_NUM_FORMS" />
       <li>
       	<label for="id_form-0-school">School:</label>
       	<input type="text" name="form-0-school" id="id_form-0-school" />
       </li>
       <li>
       	<label for="id_form-0-course">Course:</label>
       	<input type="text" name="form-0-course" id="id_form-0-course" />
       </li>
     </div>
    )} 
 })
 ReactDOM.render(<Formset/>,document.getElementById('formset'))
 </script>

Refreshing our page, we should still get exactly the same view, but now, we have taken control of the rendering of the form with React. Our custom React component is being injected into the formset id element.

Let's simplify our component a little bit.

 const Form = createReactClass({
 render(){
 	return (
      <div>
        <li>
          <label for="id_form-0-school">School:</label>
          <input type="text" name="form-0-school" id="id_form-0-school" />
        </li>
        <li>
          <label for="id_form-0-course">Course:</label>
          <input type="text" name="form-0-course" id="id_form-0-course" />
        </li>
      </div>
      )}
  })
  const Formset = createReactClass({ 
  	render(){ 
    	const management_data = [
          {name: "TOTAL", value:1,},
          {name: "INITIAL", value: 0},
          {name: "MIN_NUM", value: 0},
          {name: "MAX_NUM",value: 2}
        ]
        return (
        	<div>
              {management_data.map((d,index)=>
              	<input key={`management-${index}`} 
              		type="hidden" name={`form-${d.name}_FORMS`} 
              		value={d.value} id={`id_form-${id.name}_FORMS`} />
              )}
              <Form />
            </div>
        )} 
   })

I pulled out the actual form into a seperate component and avoided duplicating all of the hidden fields. Now, I can move the management_data out of the component and pass it as a props to the Formset component. It looks like this:

...
 const Formset = createReactClass({ 
 	render(){ 
    	return (
          <div>
          	{this.props.management_data.map((d,index)=>
          		<input key={`management-${index}`} 
          			type="hidden" name={`form-${d.name}_FORMS`} 
          			value={d.value} id={`id_form-${id.name}_FORMS`} />
          	)}
          	<Form />
          </div>
       )} 
 })
        
const management_data = [
  {name: "TOTAL", value:1,},
  {name: "INITIAL", value: 0},
  {name: "MIN_NUM", value: 0},
  {name: "MAX_NUM",value: 2}
  ]
  ReactDOM.render(
  	<Formset managemend_data={managemend_data} />,
  	document.getElementById('formset')
  )

Going back to our Form component, because of the way the Django formset actually requires formset data, the id of each form field follows a particular convention, id_form-0-school where the 0 represents fields in the same form instance. We can take advantage of this.

We need to keep track of the number of forms we have successfully added so that we can add a local state to our Formset component.

urls.PNG

In the above, we created a local state called noOfForms and set the default value to 1. Then, in the render method of the formset component, we dynamically created an array based on the current local state, filled it with undefined by using the fill method on the array prototype, and then used map to return the individual form.

I also added a button that would be responsible for adding a new form to the screen. Right now, it doesn't do anything.

In the Form component, I'm getting the index of the form passed from the parent Formset and using that to generate the id and name of fields in the form so that the Django way isn't broken.

Let's finish up the implementation by implementing the button add and remove actions.

In the Form component, I'm expecting a props that would determine if I want to remove the particular form or not from the parent, as well as the action that should occur when the remove button is clicked.

const Form = createReactClass({
    removeForm(e){
         e.preventDefault() 
        this.props.removeForm(this.props.index);
    },
  render(){
      const school = `form-${this.props.index}-school`
      const course = `form-${this.props.index}-course`
      	return (
        	<div>
            	<li>
                  <label for={`id_${school}`}>School:</label>
                  <input type="text" name={school} id={`id_${school}`} />
                </li>
                <li>
                  <label for={`id_${course}`}>Course:</label>
                  <input type="text" name={course} id={`id_${course}`} />
                </li>
                {this.props.displayRemoveButton ? 
                	<button onClick={this.removeForm}>Remove Form</button>
                	: null}
           </div>
     )}
  })

The Remove Form button has an onClick event handler that calls a method within the component called removeForm, which contains the logic for calling the passed function from the parent that knows how to delete the form. We are preventing any default browser event action from happening by callig e.preventDefault, since we do not want our form to get submitted to the server by mistake.

We'll then need to create the method for removing a form from the parent Formset.

const Formset = createReactClass({ 
  getInitialState(){
      return {
      	noOfForms:1
    }
  },
    addNewForm(e){
      e.preventDefault()
      this.setState({noOfForms:this.state.noOfForms+1})
    },
    removeForm(index){
    	this.setState({noOfForms: this.state.noOfForms-1})
    },
    render(){ 
    	return (
        	<div>
            	{this.props.management_data.map((d,index)=>
                	<input key={`management-${index}`} 
                    	type="hidden" name={`form-${d.name}_FORMS`} 
                        value={d.value} id={`id_form-${d.name}_FORMS`} />
                 )}
                 {Array(this.state.noOfForms).fill().map((form,index)=>
                 	<Form key={`form-${index}`} index={index} 
                    	displayRemoveButton={this.state.noOfForms > 1} 
                        removeForm={this.removeForm} />
                 )}
                 <button onClick={this.addNewForm}>Add new form</button>
       		</div>
         )} 
   })

Okay, our Form component is now receiving more props. The displayRemoveButton props determine whether the removeButton on the form shows or not. The removeForm props take the function that should be called by the child form when the removeForm button is clicked.

With the above implemented and the browser refreshed, we should be able to add and remove forms to the formset. We could inspect elements by using the browser dev tools to ensure that the id and name for field in each form have the index of the form and are consistent with the Django implementation.

In the next blog post, we'll look at enforcing that the maximum number of forms required by the server isn't exceeded and ensuring that everything works as expected when the form is eventually submitted. Have a lovely day!

The GitHub repo for this post is hosted at https://github.com/gbozee/react-for-backend-dev.

Discover and read more posts from Oyeniyi Abiola
get started