DEV Community

Cover image for Django Templates with React
Nick K
Nick K

Posted on

Django Templates with React

Django Templates with React

I'm relatively new to the Django world, and I love it for the amount of ready-to-use solutions. The only thing I don't quite like is Django Templates. After using React for a while, these templates feels a bit cumbersome and inelegant.

I found a few articles recommending creating full-blown React SPA and using Django just as a REST API. But there are few issues with this solution, which makes it not that attractive.

If you create full-blown React SPA, you will have code duplication right away, because there is routing here and there, there is authentication and authorization, there are error pages, there are states & data models... For some applications / teams / companies it is an acceptable and even desirable outcome, but not for me and my small application.

So, how do we use React just as a template engine?

Idea

There is a nice thing about React, it doesn't just mount to the DOM body or somewhere random, it mounts to the exact element you specify.

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

So the idea is to try to implement routing using one mounting point just for one page.

At this point, an attentive reader will notice that for each page we will actually load JavaScript for every other page because it's the same bundle, but please bear with me for a while.

Routing

So, we can specify multiple mounting points in index.js:

ReactDOM.render(
  <h1>Page 1!</h1>,
  document.getElementById('page-1')
);

ReactDOM.render(
  <h1>Page 2!</h1>,
  document.getElementById('page 2')
);
Enter fullscreen mode Exit fullscreen mode

If there is <div id="page-1"/> on the page, then the user will see the first page, but if there is <div id="page-2"/>, then the user will see the second page.

On the Django side, you will just need to render <div> block with the right ID:

{% extends "base.html" %}  

{% load static %}  

{% block content %}  
    <div id="{{ element_id }}"></div>

    <script src="{% static 'index.js' %}"></script>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode
def index_view(request):  
    if request.method == 'GET':  
    return render(
        request, 
        'react-page.html', 
        { 'element_id': 'todos-index' }
    )
Enter fullscreen mode Exit fullscreen mode

Bundle size

Now it's the time to figure out how not to load JavaScript for pages which aren't used right now.

There is actually a built-in React feature, which is called Code Splitting. You can load pages only when you actually need them, which will improve user experience.

So our pages routing will start looking something like this:


let Page1 = React.lazy(() => import('./page1'))
let Page2 = React.lazy(() => import('./page2'))

ReactDOM.render(
    <Suspense fallback={<></>}>
        <Page1/>
    </Suspense>, 
    document.getElementById('page-1')
);

ReactDOM.render(
    <Suspense fallback={<></>}>
        <Page2/>
    </Suspense>, 
    document.getElementById('page-2')
);          
Enter fullscreen mode Exit fullscreen mode

Context data

At this point we can implement guards and routing on Django side and render static pages using React. Now it's time to figure out how to pass data.

We can use Django built-in called json-script which takes some data, escapes dangerous characters and dumps it to the JSON, which is readable by JavaScript. Let's try to leverage it.

{% extends "base.html" %}  

{% load static %}  

{% block content %}  
    <div id="{{ element_id }}"></div>  
 {{ page_context | json_script:'page-context' }}  

    <script src="{% static 'index.js' %}"></script>  
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

And get this data on React side


//custom hook
export let usePageContext = <T = any>() => {  
    let [pageContext, setPageContext] = useState<T | undefined>(undefined)  
    useEffect(() => {  
        let pageContext = document.getElementById('page-context').textContent  
        setPageContext(JSON.parse(pageContext))  
    }, [])  
    return pageContext as T  
}

interface Todo {  
    id: number  
    title: string  
}  

const TodosIndexPage = memo(() => {  
    let pageContext = usePageContext<{ todos: Todo[] }>()  
    let todos = pageContext?.todos  
    return <>  
    <h1>React todos page</h1>  
    <ul>
        {todos?.map(todo => <li key={todo.id}>{todo.title}</li>)}  
    </ul>  
    </>
})  

export default TodosIndexPage   
Enter fullscreen mode Exit fullscreen mode

Final notes

That's basically it, there are only few steps to make it work:

  1. Separate mounting points for React.
  2. (Optional, but good for performance) Setup code splitting for React.
  3. Return target mounting point on Django side.
  4. Send page context on Django side.
  5. Get page context on React side.

You can find a working example on my GitHub github.com/kozlovzxc/djangoReactTemplates, but I encourage you to play with it yourself, because you probably will come up with something better!

p.s.: You can also try to duplicate Django routes using React Router instead of multiple mounting point, but I'm not sure about benefits though.


Btw, let's be friends here and on twitter 👋

Top comments (7)

Collapse
 
chat2neil profile image
chat2neil

Great work thanks. This has really helped with my project.

Collapse
 
thanasisxan profile image
Thanasis Xanthopoulos

exactly what I was looking for without extra unecesary libraries or complexity

Collapse
 
tombohub profile image
tombohub

And then you can manipulate context as a react state? How do you send data to views? Also there is project that integrates react and Django but I forgot it's name

Collapse
 
hexnickk profile image
Nick K

In my setup I just used normal Django flow with forms and POST requests for data updates and React mostly for read-only templates, so once page will be reloaded by POST request, there will be a new data in your hook.

To speed things up, you can either enforce caching policy for JS files, or update state locally and send new data to the API.

Collapse
 
momatininja profile image
momo

Is there a way to do this as single page. For example when you goes to another page music doesn't get restarted or paused?

Collapse
 
hexnickk profile image
Nick K

Haven't researched into that, maybe you can start with rendering and then transition to PWA, but this way you won't be able to pass data in templates 🤷

Collapse
 
mrmonroe profile image
Matthew Monroe

You sir, are a genius.