Building a React.js Application using Django REST Framework

March 3, 2021

In this tutorial, we will build a Todo application using React and Django. React is a front-end JavaScript framework that uses components in creating user interfaces for single-page applications. Django is a Python backend web framework used to build scalable and secure website applications.

We are going to create an application that consumes React for the user interface and Django for the API of our application using Django REST framework (DRF).

Prerequisites

Basic knowledge of React, Python(Django) is required but I’ll try and boil things down as much as possible.

You can check out these articles to get started with React and Python:

Before getting your hands dirty, you may need to:

  1. Install Python 3

  2. Install Node.js

Step 1: Backend using Django

From a terminal create a new project directory called django-react-todoApp

mkdir django-react-todoApp
cd django-react-todoApp

We must have a virtual environment activated to be able to install Django.

Let’s create a virtual environment and activate it:

pip install pipenv
pipenv shell

Install Django and create a project named backend:

pipenv install django
django-admin startproject backend

Now that we have created our project, let’s go ahead and create an application called todo and migrate the models into the database.

If everything works you should see the “Congratulations” page from Django.

Navigate to backend/settings.py and add todo to the list of INSTALLED_APPS

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo',
]

We’ll go ahead and set up our model for Todo item fields.

Modify todo/models.py as follows:

from django.db import models

class Todo(models.Model):
   title = models.CharField(max_length=100)
   description = models.TextField()
   completed = models.BooleanField(default=False)

   def _str_(self):
     return self.title

The model contains:

  • Title: What the task is.

  • Description: Give more explanation about a particular task.

  • Completed: If the task is complete the status is True otherwise it remains False.

Django uses _str_() to display a default attribute to be displayed, in our case we return title to be shown from our model.

Let us run migrations to add our model to the database schema.

python manage.py makemigrations
python manage.py migrate

Django comes with a built-in admin interface. The interface allows administrators and authorized users to perform actions directly to the objects defined in the models.

We can add models to our Admin page using the admin.site.register() functions. In the todo app’s admin.py, let’s add the model to our admin page.

from django.contrib import admin
from .models import Todo

class TodoAdmin(admin.ModelAdmin):
  list = ('title', 'description', 'completed')

  admin.site.register(Todo, TodoAdmin)

Let’s create a superuser for the admin page to login.

python manage.py createsuperuser

This will prompt you to enter the username, email, password, password(again). We can open the admin page using the following link http://localhost:8000/admin.

python manage.py runserver

admin dashboard

We can now add and delete items from the admin page. Great!

admin panel

Step 2: Putting in the APIs

You can learn more about APIs in this amazing article.

Install the djangorestframework and django-cors-headers:

pipenv install djangorestframework django-cors-headers

Add rest_framework and corsheaders to the INSTALLED_APPS in backend/settings.py file and modifiy the MIDDLEWARE:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo',
    'corsheaders',
    'rest_framework',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Add this code snippet in backend/settings.py file:

CORS_ORIGIN_WHITELIST = [
    'http://localhost:3000',
]

Django-cors-headers is an HTTP-header-based that allows a server to indicate any other origins to your Django application. Cross-origin Resource Sharing (CORS). Within the CORS_ORIGIN_WHITELIST , localhost:3000 will serve as our port.

Now, let’s create a serializer file.

A serializer is a component that converts Django models to JSON objects and vice-versa.

touch todo/serializers.py

Let’s add this to the serializers.py file:

from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id' ,'title', 'description', 'completed')

From the rest_framework package, we import the serializers. We create a class, TodoSerializer that extends from the ModelSerializer class. We then go ahead and specify the model and fields we want to be returned.

Let’s also update the todo/views.py:

from django.shortcuts import render
from .serializers import TodoSerializer 
from rest_framework import viewsets      
from .models import Todo                 

class TodoView(viewsets.ModelViewSet):  
    serializer_class = TodoSerializer   
    queryset = Todo.objects.all()     

Before creating webpages in Django we must define our URLs. A URL is an address to which a webpage is served. Defining URLs describes what requests are returned from the views when the templates are rendered in the browser.

In the backend/urls.py we define the URL routes for the API:

from django.contrib import admin
from django.urls import path,include               
from rest_framework import routers                 
from todo import views                             

router = routers.DefaultRouter()                   
router.register(r'todos', views.TodoView, 'todo')  

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls))             
]

The next module is the router.urls that provides routing for our API.

The router enables us to create the subsequent operations:

Performing CRUD operations to our items is enabled by the router.

  • /todos/ - This route return each item from our API.

  • todos/id - Returns a specific item and it’s id.

python manage.py runserver

api

We have set our backend to let us move forward to the frontend.

Step 3: Frontend using React

To install React we use the following command:

-g stands for global as we are first installing create-react-app globally:

npm install -g create-react-app

While in the parent directory - django-react-todoApp- create a React application, frontend:

create-react-app frontend

To start the server:

cd frontend
npm start

You should be able to see the default React app by now.

Next, let’s install bootstrap and reactstrap to style the user interface. You can learn more on bootstrap and reactstrap from here.

npm install bootstrap@4.6.0 reactstrap@8.9.0 --legacy-peer-deps

When we open our index.js file it should resemble the code below:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
    <App />,
    document.getElementById('root')
);

//React generated comments ignored
reportWebVitals();

ReactDOM.render() renders a React element into the DOM in the given container element. It takes two arguments. The first is the JSX being rendered, and the second displays the container element on the HTML page.

Substitute the below code in src/App.js:

import React, { Component } from "react"

const todoItems = [
  {
    id: 1,
    title: "Nature walk in the park",
    description: "Visit the park with my friends",
    completed: true
  },

  {
    id: 2,
    title: "Visit",
    description: "Got to my aunt's place",
    completed: true
  },

  {
    id: 3,
    title: "Write",
    description: "Do an article about anthropology",
    completed: true
  },
];

class App extends Component {
    constructor(props) {
      super(props);
      this.state = {todoItems};
    };

    render() {
      return (
        <main className="content">
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <ul className="list-group list-group-flush">
              {this.state.todoItems.map(item => (
              <div>
                <h1>{item.title}</h1>
                <span>{item.description}</span>
              </div>
              ))}
              </ul>
            </div>
          </div>
        </div>
      </main>
      )
    }
  }
  
export default App;

We start by rendering a list of items. The list will be artificial data but later we’ll fetch the data from the API we created in the previous steps.

We define a list of items. Each item has an id, title, description, and status of whether the task is completed or not, completed.

We introduce the class constructor where we set the initial state. In our case, the internal state is the dummy list of items, todoItems.

We use the built-in JavaScript map functionality in our JavaScript XML (JSX).

The map() method creates a new array populated with the results of calling a provided function on every element in the calling array. We use curly braces to evaluate JavaScript expressions.

The render() method when called displays the JSX. The classname attribute in the render() method enables us to use the CSS properties.

We use arrow functions because they shorten our functions declaration.

Your User Interface should resemble the one below:

frontend

It is time to consume the API we created earlier.

cd backend
python manage.py runserver

We’ll need to modify the frontend/package.json by adding proxy. A proxy is used in the development environment to facilitate communication between the server and the UI since the backend and the UI will be running on different ports.

The proxy enables us to use Django’s localhost which handles our API requests.

Let’s go ahead and add it.

[...]
"name": "frontend",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:8000", 
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.9",
    "@testing-library/react": "^11.2.3",
    "@testing-library/user-event": "^12.6.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.1",
    "web-vitals": "^0.2.4"
    "reactstrap": "^8.8.1",
  },
  [...]

To consume our API instead of the artificial data, update the frontend/src/App.js with the snippet below:

import React, { Component } from "react"

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      activeItem: {
        title: "",
        description: "",
        completed: false
      },
      todoList: []
      };
  }

    async componentDidMount() {
      try {
        const res = await fetch('http://localhost:8000/api/todos/');
        const todoList = await res.json();
        this.setState({
          todoList
        });
      } catch (e) {
        console.log(e);
    }
    }
    renderItems = () => {
      const { viewCompleted } = this.state;
      const newItems = this.state.todoList.filter(
        item => item.completed === viewCompleted
      );
      return newItems.map(item => (
        <li 
          key={item.id}
          className="list-group-item d-flex justify-content-between align-items-center"
        >
          <span 
            className={`todo-title mr-2 ${
              this.state.viewCompleted ? "completed-todo" : ""
            }`}
            title={item.description}
            >
              {item.title}
            </span>
        </li>
      ));
    };

    render() {
      return (
        <main className="content">
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <ul className="list-group list-group-flush">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
      </main>
      )
    }
  }
  
export default App;

Let’s go through each line of code to better understand what they do:

In our constructor, we created a few properties in our state object. We assigned the viewCompleted property to false since our interface only shows items marked as not complete from our API at the moment.

The activeItem property includes the title, description and passes false to completed as the default status.

We then pass an empty array to our todoList because we are going to fetch our data from an API.

First, we wrap fetch() in a try/catch block to handle any network errors. We then call fetch() with the await keyword, where we pass our API endpoints.

The async enables asynchronous operations, it returns a resolve value promise from our function.

We will define the componentDidMount() method as part of the async function. This enables us to perform each fetch using the await keyword.

The componentDidMount() function is called by React when a component is rendered on the client-side. Read more about life cycle functions from this article.

The setState() method in the componentDidMount() function is called when we want to update a change to our previous state in the application.

We create a renderItems() function that uses the filter built-in array functionality, to show the completed items from our todoList. The filter function takes a function to evaluate each item in the list.

We define a variable newItems to store the items which we display by using the map functionality. We use a ternary conditional operator to show if an item description is marked as complete or not.

Ternary is a JavaScript operator that returns true in the first condition and false in the second part of an expression.

In our render() method we display the items through the renderItems() function.

The consumed data from the API should be displayed as follows:

api_data

To handle actions such as adding tasks and marking them complete, we can create a modal component.

Modal enables us to create custom content such as popovers or dialog boxes in our applications.

Let’s go ahead and create a components folder in the src directory then create a file in it called Modal.js:

mkdir src/components
cd components
touch Modal.js

Let’s add this to the file:

import React, { Component } from "react";
import {
    Button,
    Modal,
    ModalHeader,
    ModalBody,
    ModalFooter,
    Form,
    FormGroup,
    Input,
    Label

} from "reactstrap";

export default class CustomModal extends Component {
    constructor(props) {
        super(props);
        this.state = {
            activeItem: this.props.activeItem
        };
    }
    handleChange = e => {
        let { name, value } = e.target;
        if (e.target.type === "checkbox") {
            value = e.target.checked;
        }
        const activeItem = { ...this.state.activeItem, [name]: value };
        this.setState({ activeItem });
    };
    render() {
        const { toggle, onSave } = this.props;
        return (
            <Modal isOpen={true} toggle={toggle}>
                <ModalHeader toggle={toggle}>Todo Item</ModalHeader>
                <ModalBody>
                    <Form>
                        <FormGroup>
                            <Label for="title">Title</Label>
                            <Input 
                              type="text"
                              name="title"
                              value={this.state.activeItem.title}
                              onChange={this.handleChange}
                              placeholder="Enter Todo Title"
                            />
                        </FormGroup>
                        <FormGroup>
                            <Label for="description">Description</Label>
                            <Input
                            type="text"
                            name="description"
                            value={this.state.activeItem.description}
                            onChange={this.handleChange}
                            placeholder="Enter Todo description"
                            />
                        </FormGroup>
                        <FormGroup check>
                            <Label for="completed">
                                <Input
                                type="checkbox"
                                name="completed"
                                checked={this.state.activeItem.completed}
                                onChange={this.handleChange}
                                />
                                Completed
                            </Label>
                        </FormGroup>
                    </Form>
                </ModalBody>
                <ModalFooter>
                    <Button color="success" onClick={() => onSave(this.state.activeItem)}>
                        Save
                    </Button>
                </ModalFooter>
            </Modal>
        );
    }
}

In the code above we first import React and the components from reactstrap that we installed earlier. In the constructor, we use the property that we created earlier in the App.js file. The props keyword passes the argument to the activeItem component as objects.

The handleChange method takes note of a change in the state of a React component, takes the event as a parameter, and does something to change the state.

We use destructuring assignment to create a checkbox where users can click to mark a task as complete. We then change the activeItem in our state object by setState() method.

In our render() method we pass toggle and the onSave() method to props. We return the Modal component when toggled.

We add the toggle component in the ModalHeader to enable dropping the modal. In the ModalBody, we add the forms for adding the item title and item description.

In each FormGroup, we specify the activeItem value. We use the onChange event to detect when the input value changes and returns the target input’s name and value.

Since the last FormGroup's input type is a checkbox the target value will be checked as we assigned it in our handlechange() method.

In the ModalFooter we will create a button to save our items using the onSave() method.

We can then create the add task and mark as completed functionalities in App.js.

Before we continue let’s install axios. It allows our applications to make a request to external endpoints. We use it to perform CRUD operations to our API.

npm install axios@0.21.1

In the App.js add the code snippet below:

import React, { Component } from "react"
import Modal from "./components/Modal"; 
import axios from "axios";

class App extends Component {
    state = {
      viewCompleted: false,
      activeItem: {
        title: "",
        description: "",
        completed: false
      },
      todoList: []
    };

    async componentDidMount() {
      try {
        const res = await fetch('http://localhost:8000/api/todos/');
        const todoList = await res.json();
        this.setState({
          todoList
        });
      } catch (e) {
        console.log(e);
    }
    }

    toggle = () => {
      this.setState({ modal: !this.state.modal });
    };
  
    //Responsible for saving the task
    handleSubmit = item => {
      this.toggle();
      if (item.id) {
        axios
          .put(`http://localhost:8000/api/todos/${item.id}/`, item)
        return;  
      }
      axios
        .post("http://localhost:8000/api/todos/", item)
    };

    createItem = () => {
      const item = {title: "", description: "", completed: false };
      this.setState({ activeItem: item, modal: !this.state.modal });
    };

    displayCompleted = status => {
      if (status) {
        return this.setState({ viewCompleted: true});
      }
      return this.setState({ viewCompleted: false});
    };
    renderTabList = () => {
      return (
        <div className="my-5 tab-list">
          <button 
            onClick={() => this.displayCompleted(true)}
            className={this.state.viewCompleted ? "active" : ""}
          >
            Complete
          </button>
          <button 
            onClick={() => this.displayCompleted(false)}
            className={this.state.viewCompleted ? "" : "active"}
          >
            Incomplete
          </button>
        </div>  
      );
    };

    renderItems = () => {
      const { viewCompleted } = this.state;
      const newItems = this.state.todoList.filter(
        item => item.completed === viewCompleted
      );
      return newItems.map(item => (
        <li 
          key={item.id}
          className="list-group-item d-flex justify-content-between align-items-center"
        >
          <span 
            className={`todo-title mr-2 ${
              this.state.viewCompleted ? "completed-todo" : ""
            }`}
            title={item.description}
            >
              {item.title}
            </span>
        </li>
      ));
    };

    render() {
      return (
        <main className="content">
        <h1 className="text-white text-uppercase text-center my-4">Todo App</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="">
                <button onClick={this.createItem} className="btn btn-success">Add Task</button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
        {this.state.modal ? (
          <Modal
            activeItem={this.state.activeItem}
            toggle={this.toggle}
            onSave={this.handleSubmit}
          />
        ): null}
      </main>
      )
    }
  }
  
export default App;

First, import the Modal that we created earlier and axios. The toggle() method changes the Modal state when toggled, if the expression is true it returns the properties defined in the Modal in Modal.js, otherwise nothing happens. We add this in the render() method.

The handleSubmit() saves our items to the API, we use axios to make the requests to it. We use PUT to insert the item into the already existing list of items according to the item id.

We then create a createItem() method to add our task which is defined in the render() method.

The displayCompleted() method checks the status of the viewCompleted we created in our state earlier and returns true or false.

The renderTabList() method define two buttons Complete and Incomplete, if the viewCompleted() method returns true, the item is Complete. If it returns false then the item is Incomplete. We had stated earlier how the renderItems() method works.

Our render() method returns renderTabList(), renderItems() methods and the Add Task functionality which uses the createItem() method to allow users to add task.

Your application should be like the one below at this point:

frontend

Step 4: Testing

Let’s start our backend server made using Django by running the following commands:

cd backend
pipenv shell
python manage.py runserver

Let’s start our React application by running the following commands:

npm start

Conclusion

We have learned how to integrate a Django application with React serving as the frontend.

You can learn more on Django and React from this article (Jordan Irabor, 2020).

Happy coding.


Peer Review Contributions by: Mohan Raj


About the author

Oruko Pius

Oruko is an Undergraduate Computer Science student in senior year. He is passionate about Web technologies and developing web native applications, data analytics and computer architectural design. He is open to talks about trending topics around the technology space.

This article was contributed by a student member of Section's Engineering Education Program. Please report any errors or innaccuracies to enged@section.io.