Create React module in Odoo - standalone Todo app

Odoo provides an extensible framework to create custom modules. For most customization needs, a module extends the pre-dominant form structure of Odoo by defining form and table fields. But there is case the UI need to deviate radically, for example, the point_of_sale module.

Modules like point_of_sale create a standalone single page app that plug into Odoo's backend. Basically it is like any other React app, except that the app authenticates and fetches data via Odoo's api. Let us illustrate the process by walking through implementing a Todo app, the classical FE getting started guide. The steps are:

  • First, create a controller endpoint to serve the todo app
  • Second, implement the React UI

Create a controller endpoint

Like point_of_sale, our Todo app resides in a separate endpoint from Odoo's common admin interface, which allows us to implement a new UI from a blank slate.

Let's create a simple controller with a single get method. And all it does is return a simple html template, which then bundles all javascript needed to bootstrap the todo app.

# -*- coding: utf-8 -*-
from odoo import http
from odoo.http import request
import json

class Todo(http.Controller):
    @http.route('/todo/todo/', auth='public')
    def index(self, **kw):
        context = {
            'session_info': json.dumps(request.env['ir.http'].session_info())
        }
        # todo.index is the xml template that contains
        # main html content 
        return request.render('todo.index', qcontext=context)

todo.index is the template that contains main html document.

<template id="index" name="todo index">&lt;!DOCTYPE html&gt;
    <html>
        <head>
	        <title>Custom React Todo</title>
	        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
	        <meta http-equiv="content-type" content="text/html, charset=utf-8" />               
	    </head>
	    <body>
           	<div class="o_main_content" id="todos-example"/>
            <script type="text/javascript">
                var odoo = <t t-out="json.dumps({
                    'debug': debug,
                    })"/>;
                // Prevent the menu_service to load anything. In an ideal world, POS assets would only contain
                // what is genuinely necessary, and not the whole backend.
                odoo.loadMenusPromise = Promise.resolve();
            </script>
            <t t-call-assets="web.assets_common" t-css="false" />
            <script src="https://unpkg.com/react@18/umd/react.development.js" ></script>
            <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" ></script>                   
            <t t-call-assets="todo.assets"/>
        </body>
    </html>
</template>

With the endpoint setup, you can access your new app at http://localhost:8069/todo/todo/, just that it is empty for now. The next step is to setup the interface.

Implement the React interface

We have a html template now. As with any other React app, next step is to loading the main javascript file that contains React code. Assuming that you know React well, creating a Todo app is straightforward.

With Odoo, we will need to put all our javascripts and css into an asset bundle, and tell Odoo to inject it into html. In the template above, it is done with the line <t t-call-assets="todo.assets"/>. It tells Odoo QWeb renderer that it should inject the javascript bundle todo.assets at that line in the output html.

To create the bundle, with Odoo 16, declare it in the __manifest__.py file

{
   'assets': {
        'todo.assets': [
            'todo/static/src/index.js',
            'todo/static/src/components/*.js',
            'todo/static/src/index.css',
            'todo/static/src/components/*.css'
        ]
    }
}

One thing to , Odoo's javascript module is declared using its own odoo.define syntax. If you want to write your javascript in standard ES6 import style, add the magic comment at the top of your file.

/* @odoo-module */

import React from 'react';
import ReactDOM from 'react-dom/client';

A problem remains, as React app is often implemented in JSX, which Odoo's bundler does not understand yet. It is possible to manually transpile Jsx to javascript first and include js file in the template bundle instead. What a slow and unpleasant process it would be.

So, next step, we attempt to override Odoo asset bundler’s with our own and call Babel transpiler before bundling.

Run Babel transpiler in Odoo's javascript bundling

Since you are interested in making Odoo React app, Babel must be in your regular toolkit. Babel is used to transpile jsx syntax to plain old javascript.  

Basically, we need to tell Odoo to run Babel transpiler before bundling javascript files together. Todo that, extend the base QWeb class to override the default AssetsBundle and JavascriptAsset. You can find more detailed instruction on how to run Babel transpiler on Odoo's asset here.

Now starts Odoo and navigate to the endpoint we created earlier to see the result.

What's next

Now that we are able to create a React-based Todo app, you may want to consider:

  • Save todo item in Odoo's backend
  • What if you want the interface to be part of Odoo usual admin page.
💡
Demo code can be found here. Sample module tested on Odoo 16