Picking up where we left off, we will see how to share initial data with the React app, set up CORS for development, and make AJAX calls with axios.

Quick update

I have updated the collectbase command and renamed it build; (the full code is on GitHub).

Let's build on our previous django-react-boilerplate project. Here is where we left off last time:

How would you test this?

Testing this setup is as simple as testing any other Django app.
Open the file myapp/tests.py and paste the following code:

from django.test import TestCase
from django.urls.base import reverse_lazy

index_path = reverse_lazy('index')

class Test(TestCase):
    def setUp(self) -> None:
        super().setUp()

    def test_index_has_react_root(self):
        r = self.client.get(index_path)
        self.assertContains(r, '<div id="root"></div>')

From the root folder, run the test with the following command (Make sure the environment is activated):

./manage.py test

Our first test passes with flying colors, and you should see the following output on the command line:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.015s

OK
Destroying test database for alias 'default'...

Sharing data with React

You can share data with React:

  • using the json_script template tag
  • via REST

The json_script tag

Django provides a template tag to pass Python objects as JSON to javascript code safely. From the docs:

{{ value|json_script:"hello-data" }}

This will wrap the value with a <script id="hello-data" type="application/json"> tag. Notice the tag used here is json_script instead of safe or escapejs.

More tests

Add two additional test methods to the Test class.

def test_index_template_has_sharedData(self):
    r = self.client.get(index_path)
    self.assertContains(r, 'sharedData')

def test_index_context_has_sharedData(self):
    r = self.client.get(index_path)
    self.assertIn('sharedData', r.context.keys())

Those tests should fail. To make the tests pass, update the config/urls.py file, so it looks like this:

from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static

from django.views.generic import TemplateView

class IndexView(TemplateView):
    template_name = 'base.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        #  This is data we want to share with react
        context['sharedData'] = {
            'first_name': 'Michaël',
            'message': 'Welcome to my awesome website.'
        }

        return context


urlpatterns = [
    path('admin/', admin.site.urls),

    path('', IndexView.as_view(), name='index')
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

We used get_context_data method to pass a dictionary to the base.html template:

context['sharedData'] = {
    'first_name': 'Michaël','message': 'Welcome to my awesome website.'
}

Update the base template

Open up the templates/base.html file and update the block head section:

{% extends 'base-react.html' %}

...

{% block head %}
<title>Django react app</title>

{% comment %} Add this line {% endcomment %}
{{ sharedData|json_script:"sharedData" }}

{% endblock head %}
...

This is where we use the json_script tag mentioned earlier.

{{ sharedData|json_script:"sharedData" }}

Django will then render the tag as:

<script id="sharedData" type="application/json">
    { "first_name": "Micha\u00ebl", "message": "Welcome to my awesome website." }
</script>

Our React app can now pick that information up by parsing the HTML page. However, before moving on to the next section, make sure our tests run fine with ./manage.py test:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 3 tests in 0.054s

OK
Destroying test database for alias 'default'...

Parse JSON data from a Django HTML page

Move into the client folder (your React app) and install axios.

cd client
npm install axios

Create a new file called utils.jsx in the client/src folder:

touch src/utils.jsx

Add the following code to the client/src/utils.jsx file:

import axios from "axios";

export const IS_DEV = process.env.NODE_ENV !== "production";

export const DOMAIN = IS_DEV ? `http://127.0.0.1:8000` : `${window.location.origin}`;

export const getSharedData = () =>
    new Promise(async (resolve, reject) => {
        let sharedData = document.getElementById("sharedData");

        if (!sharedData && IS_DEV) {
            // This will not run in production
            console.log(`Getting page shared data in ${process.env.NODE_ENV} mode`);

            const base = new URL(window.location.href);
            const url = `${DOMAIN}${base.pathname}${base.search}`;

            let res;
            try {
                res = await axios.get(`${url}`);
            } catch (err) {
                return reject(err);
            }

            if (!res || res.status !== 200) return reject("No data");

            if (res.headers["content-type"].includes("text")) {
                const parser = new DOMParser();
                const doc = parser.parseFromString(res.data, "text/html");
                sharedData = doc.getElementById("sharedData");
            }
        }
        if (sharedData) {
            return resolve(JSON.parse(sharedData.textContent));
        }

        return reject("No data");
    });

To retrieve the initial data from Django, we call the getSharedData helper function in useEffect like this:

useEffect(() => {
    getSharedData()
        .then((data) => {
            // Do something with data
            console.log(data);
        })
        .catch((err) => {
            // Handle error
            console.log(err);
        });
}, []);

Open the client/src/App.js file, delete everything and paste:

import { useEffect, useState } from "react";
import { getSharedData } from "./utils";

import "./App.css";

function App() {
    const [sharedData, setSharedData] = useState({ isLoaded: false });

    useEffect(() => {
        getSharedData().then((data) => {
            console.log(data);
            setSharedData({ ...data, isLoaded: true });
        });
    }, []);

    return (
        <div className="App">
            <header className="App-header">
                <p>Hello!</p>
                <Firstname {...sharedData} />
                <Message {...sharedData} />
            </header>
        </div>
    );
}

const Firstname = ({ first_name }) => {
    if (!first_name) return <div>Loading ....</div>;

    return <div>I am {first_name}</div>;
};

const Message = ({ isLoaded, message }) => {
    if (!isLoaded) return null;
    return <div>{message}</div>;
};

export default App;

If your React server is not currently running, start it with npm start. Likewise, open another terminal tab (activate the virtual environment) and start the Django server with ./manage.py runserver.

Now, open your browser and navigate to http://127.0.0.1:3000/. You should see this screen:

If you check the Dev Tools Network tab, you will realize that a request to http://127.0.0.1:8000/ was made and failed with a CORS error status.

We should fix that.

Fix CORS error in development mode

Add a new middleware.py file to the config folder:

cd ..
touch config/middleware.py

Open the config/middleware.py file and add:

from django.conf import settings

class CORSMiddleware(object):

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        response = self.get_response(request)

        try:
            response["Access-Control-Allow-Origin"] = settings.CORS_ORIGIN
            response["Access-Control-Allow-Headers"] = "X-CSRFTOKEN, x-requested-with, Content-Type, Accept, Origin"
            response["Access-Control-Allow-Methods"] = "OPTIONS, GET, POST, PUT, DELETE, PATCH"
            response["Access-Control-Max-Age"] = 86400
            response["Access-Control-Allow-Credentials"] = 'true'
        except Exception:
            pass

        return response

Next, open the config/settings/local.py file and update the MIDDLEWARE and CORS_ORIGIN settings. Keep in mind that you shouldn't add these settings in production. This is strictly for local development.

...
CORS_ORIGIN = 'http://127.0.0.1:3000'
...
...

MIDDLEWARE = [
    # CORS
    'config.middleware.CORSMiddleware',

    *MIDDLEWARE,
]

Go back to the browser, reload the page

In the console, you should see the following output:

[HMR] Waiting for update signal from WDS...
Getting page shared data in development mode

{first_name: "Michaël", message: "Welcome to my awesome website."}

Notice the last line is the dictionary we passed earlier to the context with get_context_data.
You have now successfully passed initial data from Django to your React app. If you're still not convinced, change the sharedData dictionary in config/urls.py as you please, and reload the page to validate that the response was correct.

REST API

You can also communicate with your server using REST. For example, let's pretend that my first name is sensitive information that I do not want to share as initial data but still need to display on the page. First, we would create our API endpoint.

Update the config/urls.py file once again, so it matches this:

from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.http import JsonResponse
from django.conf.urls.static import static

from django.views.generic import TemplateView


class IndexView(TemplateView):
    template_name = 'base.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        #  This is data we want to share with react
        context['sharedData'] = {'message': 'Welcome to my awesome website.'}
        return context


def firstname(request):
    return JsonResponse({'first_name': 'Michaël'})


urlpatterns = [
    path('admin/', admin.site.urls),

    path('api/firstname/', firstname, name='firstname'),
    path('', IndexView.as_view(), name='index')
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Our endpoint is at http://127.0.0.1:8000/api/firstname/. Now in client/src/App.js, import axios and DOMAIN and update the Firstname component:

...

import axios from "axios";
import { DOMAIN, getSharedData } from "./utils"

...

const Firstname = () => {
    const [firstName, setFirstname] = useState(null);

    useEffect(() => {
        axios.get(`${DOMAIN}/api/firstname/`).then((res) => {
            const { data } = res;
            if (data.first_name) setFirstname(data.first_name);
        });
    }, []);

    if (!firstName) return <div>Loading ....</div>;

    return <div>I am {firstName}</div>;
};

Finally, build the project:

./manage.py build

Au revoir

In the next part, we will discuss other general concepts such as Django rest framework, React portals, router dom, redux, code splitting, etc.

I hope this was helpful, thank you for reading.