DEV Community

Cover image for Django Templates with React
Nikita Kozlov
Nikita Kozlov

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 👋

Discussion (0)