How To Use React In Django Templates

In this tutorial, we will build a react app and learn how to embed it in a normal Django template. We will cover the basics of setting up a build tool, structuring the project, working with the backend API, and more.

Prerequisites

LEVEL - πŸ’» πŸ’» Intermediate-Advanced. This will not be a step-by-step tutorial but we will cover the main concepts. The tutorial assumes knowledge of Django and React.

The complete code for this tutorial can be found here.

What are we building

We will build a chart component that fetches external sensor data from the backend and displays it on the frontend. We will be using Reactβš›οΈ for the chart component and Vite⚑ as our frontend build tool. Vite provides fast dev server, jsx support out of the box, and a bunch of other cool features. Check out their docs for more info.

A react chart component

Core concepts

Javascript build tools find, process, and combine javascript source modules into files that can run in the browser. Popular tools include webpack, Rollup, snowpack, Parcel, and Vite. Choosing the right tool is a matter of personal preference. In this tutorial, we will be using Vite and the django_vite library.

The django_vite library provides a set of templatetags for serving Vite assets in Django HTML templates. We will revisit this later when we set up the template.

Vite consists of two major parts.

  • a dev server that provides features such as fast hot module replacement (HMR)
  • a build command which uses Rollup, pre-configured to bundle highly optimized production assets.
How Vite & Django serve static files and assets to the frontend

Notes to explain the above

  • the green part - this is the standard Django setup. When you run python manage.py runserver 8000 the Django dev server & the staticfiles app will serve your HTML & static files to the browser. In this example, js/site.js will be served provided to your browser from http://localhost:8000/static
  • the orange section - the Vite dev server will pre-bundle and serve your source files to your browser.

Set up the project

In this section, we will clone the repository, organize our project folder structure and frontend tooling.

Setup our development environment

Clone the repo to your local machine and follow the instructions in README.md on how to set up your local development environment.

Clone the project

mk dir django_react
cd django_react

git clone https://github.com/advantch/django-react-vite .

Create virtual env and install python dependencies

python3 -m venv .venv
source ./.venv/bin/activate

pip install -r requirements.txt

Project folder/file structure

oject folder/file structure
β”œβ”€django_react
|   └── config #
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ settings.py
β”‚       β”œβ”€β”€ urls.py
β”‚       β”œβ”€β”€ asgi.py
β”‚       β”œβ”€β”€ api.py 
β”‚       └── wsgi.py
|   └── apps # All apps in this folder
β”œβ”€β”€β”€ templates
|   β”œβ”€β”€ base.html
|   β”œβ”€β”€ home.html
|   └── dashboard
|      β”œβ”€β”€ components
|      β”œβ”€β”€ layout.html
|      β”œβ”€β”€ home.html
|   └── components
|      β”œβ”€β”€ header.html
|      β”œβ”€β”€ logo.html
β”œβ”€β”€β”€β”€ static -> static assets
|   β”œβ”€β”€ js
|   β”œβ”€β”€ css
|   β”œβ”€β”€ dist -> frontend build assets go here
|   └── images
β”œβ”€β”€β”€β”€ assets # frontend assets
|   β”œβ”€β”€ js
|   β”œβ”€β”€ css
β”œβ”€β”€β”€β”€ vite.config.js # vite config
β”œβ”€β”€β”€β”€ package.json # node
β”œβ”€β”€β”€β”€ assets # 
└─── manage.py

Notes

  • assets folder - our frontend javascript assets go here. Keep this separate from the project's static files.
  • vite.config.js -configuration for vitejs
  • package.json - metadata about the project

Configure Vite & django_vite

There is additional configuration required before we can use the django_vite library. This is already included in the starter repository. We will review the important sections below.

in settings.py

INSTALLED_APPS = [
    "django.contrib.admin",
    ......
    "django_vite"
]
# STATIC
STATIC_URL = "/static/"

STATICFILES_FINDERS = [
    "django.contrib.staticfiles.finders.FileSystemFinder",
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
]

# Where ViteJS production assets are built.
DJANGO_VITE_ASSETS_PATH = BASE_DIR / "static" / "dist"

# use HMR or not.
DJANGO_VITE_DEV_MODE = DEBUG

# Name of static files folder (called by python manage.py collectstatic)
STATIC_ROOT = "staticfiles"

# Include DJANGO_VITE_ASSETS_PATH in STATICFILES_DIRS list
STATICFILES_DIRS = [
    str(BASE_DIR / "static"),DJANGO_VITE_ASSETS_PATH
]

Notes

  • INSTALLED_APPS - include django_vite in your installed apps.
  • DJANGO_VITE_ASSETS_PATH - the is the destination folder for production assets
  • DJANGO_VITE_DEV_MODE - this tells the library to either let the Vite client-server serve your assets with HMR enabled or serve production assets from the static/dist folder.
  • STATICFILES_DIRS - Includes DJANGO_VITE_ASSETS_PATH.Instructs Django staticfiles app to also check this folder when searching for static files.

Next, we will look at the vite.config.js file. This is where we include settings for the dev server as well as configuration for the build command.

vite.config.js

const { resolve } = require('path');
import react from '@vitejs/plugin-react'

module.exports = {
  plugins: [react()],
  root: resolve('./assets'),
  base: '/static/',
  server: {
    host: 'localhost',
    port: 3000,
    open: false,
    watch: {
      usePolling: true,
      disableGlobbing: false,
    },
  },
  resolve: {
    extensions: ['.js', '.json', '.jsx', '.ts', '.tsx'],
  },
  build: {
    outDir: resolve('./static/dist'),
    assetsDir: '',
    manifest: true,
    emptyOutDir: true,
    target: 'es2015',
    rollupOptions: {
      input: {
        main: resolve('./assets/js/main.jsx'),
      }
    },
  },
};

Notes

The config file looks daunting, but it is quite straightforward once you understand the different config options. We suggest you read the Vite config reference for a comprehensive overview. To summarise the relevant bits:

  • plugins:[react()] - the react plugin to be used in the project. Refer to the docs for more information on plugins.
  • root - project root
  • base - base url where the static assets will be served from during development.
  • server - settings for the dev server are included here, including port, host
  • resolve - list of file extensions to try for imports
  • build.outDir - specifies the output directory for your build files.
  • build.rollupOptions.input.main - the bundle's entry points (e.g. main.jsx), if an array is provided the output will be mapped to separate chunks. See the rollup.js docs for more info.

package.json

 .....
"scripts": {
    "dev": "vite",
    "build": "vite build"
  },
...

Notes

  • "scripts" - runningnpm run dev will start the dev server. When you want to build your production assets run npm run build

Django html templates & URL configuration

config/urls.py

urlpatterns = [
    ....
    path("vite/", TemplateView.as_view(template_name="vite.html"), name="vite"),
    ...
]

Notes

  • This will be our React view. We are using the built-in Django TemplateView class for the view with vite.html as the template.

The project includes a base template that has HTML/CSS/JS that is reused in all site pages.

templates/base.html

{% load static i18n %}
<!DOCTYPE html>
{% get_current_language as language_code %}

<html lang="{{ language_code }}">

<head>
  <link href="https://cdn.jsdelivr.net/npm/daisyui@1.16.2/dist/full.css" rel="stylesheet" type="text/css" />
  <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2/dist/tailwind.min.css" rel="stylesheet" type="text/css" />

</head>

<body>
....
{% block footer_js %}

  <script type="text/javascript" src="{% static 'js/cookie.js' %}"></script>
  <script src="https://unpkg.com/swagger-client"></script>
  <script>
    const csrftoken = getCookie('csrftoken');
    const authHeaders = {'X-CSRFToken': csrftoken}

    // initialise swaggerjs client to interact with the api
    const client = SwaggerClient('/api/openapi.json', {
      requestInterceptor: (req) => {
        req.headers['X-CSRFToken'] = csrftoken;
        req.headers['Content-Type'] = 'application/json';
        return req;
      },
    })
    // attach to window object for easy access from any component
    window.client = client;

  </script>

{% endblock %}

</body>
</html>

Notes

  • const client - SwaggerClient('/api/openapi.json/', - set up a swagger-js client and attach it to the window object.
  • if django authentication is enabled, the client can use SessionAuthentication to interact with the API. This means you use the login_required decorator or LoginRequiredMixin in your react view on the backend to only allow authenticated users to access the view. You can then call window.client.then(client => client.apis.charts.getSensorData() directly from your react component without worrying about authenticating with tokens.

Almost done. Let's look at the template we will use for our react view.

templates/vite.html

{% extends 'base.html' %}{% load django_vite %}
{% block title %}Vite React Echarts Example{% endblock %}

{% block head_js %}
      <script type="module">
      import RefreshRuntime from 'http://localhost:3000/@react-refresh'
       RefreshRuntime.injectIntoGlobalHook(window)
       window.$RefreshReg$ = () => {}
       window.$RefreshSig$ = () => (type) => type
       window.__vite_plugin_react_preamble_installed__ = true
      </script>
      
    {% vite_hmr_client %}
{% endblock %}

{% block content %}
      {% include "components/nav.html" with btn_text="connected" %}
      <section class="container mx-auto pt-12 py-8 px-4 lg:px-6 xl:px-12">
        <div class="flex flex-row p-10">
            <div id="chart-container" class="w-full md:w-1/2 h-96"></div
            <div id="controls-container" class="w-full md:w-1/2 h-96"></div
        </div
      </section>
{% endblock %}
{% block footer_js %}
    {% vite_asset 'js/main.jsx' %}
    {{block.super}}
{% endblock %}

Notes

  • {% extends 'base.html' %} - extends the base.html template provided earlier
  • {% load django_vite %} - loads django_vite templatetags, you will need this on any template where you will be using django_vite tags
  • {% vite_hmr_client %} - this template tag generates a script tag for the HMR Vite client which will watch your assets for changes and reload them in place. When using react, you will need to include the script snippet import RefreshRuntime.. at the top. See more on that here.
  • <div id="chart-container" ..> - the container where our chart component will be rendered
  • {% vite_asset 'js/main.jsx' %} - the entry point file contains the main logic for the module. You will note that the path is set as 'js/main.jsx' and not 'static/src/js/main.jsx'. This is because the template tag will load the file from the Vite project root which is set in vite.config.js.

Add a backend endpoint for sensor data

Our front end will fetch sensor data from the backend. Since we don't have an actual sensor, we will mock this in our backend. Each request to this endpoint will return a unique value. We are using the Django ninja library to quickly build an API. You could also use the django-rest-framework library.

config/api.py

from random import randint
from ninja import NinjaAPI

api = NinjaAPI(csrf=True)

@api.get("/chartData",operation_id="getSensorData", tags=["charts"])
def get_chart_data(request):
    """Mock sensor data for frontend"""
    chart_values = [randint(300, 1000) for i in range(7)]
    return {"data": chart_values}

config/urls.py

....
from .api import api

urlpatterns = [
    ....
    path("api/", api.urls), # include api urls
]

Notes

  • Here we added the API endpoint and updated the URL config.
  • api = NinjaAPI(csrf=True) - this endpoint will only allow requests with a valid CSRF token.
  • authentication - you can visit the django-ninja docs on how to implement authentication for the API.

Building the React Chart Component

We are now ready to build the react component. We will be using the beautiful Apache Echarts library along with the react echarts-for-react wrapper to render our charts.

Install dependencies and add chart options.

npm install --save echarts-for-react echarts

static/src/js/components/options.js

const CHART_OPTIONS = {
  title: {
    text: "Factory Sensor Data",
  },
  toolbox: {
    show: true,
    feature: {
      dataView: { readOnly: false },
      restore: {},
      saveAsImage: {},
    },
  },
  grid: {
    top: 60,
    left: 80,
    right: 60,
    bottom: 45,
  },
  xAxis: {
    type: "category",
    name: "Factory area",
    nameLocation: "middle",
    nameTextStyle: {
        verticalAlign: "middle",
        fontWeight: "bold",
    },
    nameGap: 35,
    bondaryGap: true,
    data: [
      "lobby",
      "hallway",
      "office",
      "floor",
      "roof",
      "storage",
      "grounds",
    ],
  },
  yAxis: {
    type: "value",
    name: "kwh",
    nameGap: 45,
    nameLocation: "middle",
    nameTextStyle: {
      verticalAlign: "middle",
      fontWeight: "bold",
    },
    max: 1200,
    min: 300,
  },
  series: [
    {
      data: [820, 932, 901, 934, 1290, 1233, 1320],
      type: "bar",
      smooth: true,
    },
  ],
  tooltip: {
    trigger: "axis",
  },
};

export default CHART_OPTIONS;

Notes

Create the Chart Component

The chart component is a simple react component which will fetch live sensor data from the backend every 500 milliseconds. If you need a refresher on react visit the docs.

static/src/js/components/chart.jsx

import React from 'react';
import ReactECharts from 'echarts-for-react';
import CHART_OPTIONS from './options.js';

class Chart extends React.Component  {

  constructor(props) {
    super(props);

    this.state = {
      chartData: CHART_OPTIONS,
      autoRefresh: true
    }
  }

  async refreshOnInterval() {
    setInterval(()=> this.state.autoRefresh ? this.refreshOptions() : null, 500);
  }

  toggleAutoRefresh() {
    this.setState({autoRefresh: !this.state.autoRefresh});
  }

  async fetchbackendChartData(){
    return await window.client.then(client => client.apis.charts.getSensorData()
        .then(res => res.obj.data)
        .catch(err => console.error(err))
    )
  }

  async refreshOptions() {

    let chartOptions = JSON.parse(JSON.stringify(CHART_OPTIONS));
    let data = await this.fetchbackendChartData();
    chartOptions.series[0].data = data;

    this.setState({chartData: chartOptions});
  }

  componentDidMount() {
    this.refreshOnInterval();
  }

  render() {
    return <div>
      <ReactECharts option={this.state.chartData} />
      <div className="flex">
        <div className="p-4 card bordered">
          <div className="form-control">
            <label className="cursor-pointer label">
              <span className="label-text mr-2">Show Live Data</span> 
              <input onChange={()=>this.toggleAutoRefresh()} 
                     value={this.state.autoRefresh} 
                     checked={this.state.autoRefresh} type="checkbox" 
                     className="toggle toggle-primary toggle-sm"/>
            </label>
          </div>
        </div>
        <div className="btn btn-primary btn-sm mt-5" disabled={this.state.autoRefresh}  onClick={()=> this.refreshOptions()}>Refresh</div>
      </div>
    </div>;
  }
};

export default Chart;

Notes

  • this.state - initialize component state with chart options and autoRefresh boolean
  • fetchBackendChartData - retrieve backend data from the API. You will note that we are using the swagger.js client we declared and attached to the window object in templates/base.html
  • refreshOnInterval - if autoRefresh is set to true, fetch new chart data every 500milliseconds
  • toggleAutoRefresh - boolean value for whether the component should show live data
  • refreshOptions - update chart options data based on values from the backend
  • className attributes - we are using tailwindcss & daisyui attributes here. The stylesheets are included in the head section of templates/base.html.

Add the Component to our entry point file

static/src/js/main.jsx

import React from 'react';
import ReactDOM from "react-dom";
import Chart from './components/chart.jsx'

const domContainer = document.querySelector('#chart-container');
ReactDOM.render(<Chart/>, document.getElementById('chart-container'));

Notes

  • ReactDOM.render(<Chart/> - this will render the react Chart element into the DOM in the provided container, i.e. element with id chart-container and mount the Chart component.

Run the app

To run the app you will need to run both the django development server and the vite dev server.

$ python manage.py runserver
$ npm run dev # in another terminal

Go to http://localhost:8000/vite/ and your app should running.

React component in Django template

Conclusion

That's it, in this post we have learned how to include a react app in a Django project. We covered the basics of structuring our codebase and setting up a build tool for the project.

A note on deploying to production

When deploying to production, run npm run build to compile your production assets. In this example, we used a CDN for our TailwindCSS and DaisyUI stylesheets. In production, you can also use Vite to minify your production CSS assets. Visit the tailwind docs for how to install and configure tailwind for use with a build tool.

Additional Reading material

Last Updated 25 Sep 2022