Back to Top

How to build Odoo category page using ReactJS

Updated 18 October 2024

Building an Odoo category page using React.js involves creating a Odoo headless eCommerce solution that communicates with Odoo’s backend, typically via its Odoo REST API.

Introduction

Odoo Headless development is a system that splits the frontend and backend of an Odoo e-commerce site, boosting its efficiency, speed, and user-friendliness

This separation improves scalability by using the strengths of each part, leading to a better and more enjoyable user experience.

Webkul Odoo REST API offers a varient of API endpoints that allow you to perform different actions like ‘products’‘category-list’, and ‘cart’ but we’ll work with the ‘category-list’ endpoint.

Getting Start

We will guide you through this blog step-by-step that how to build category page using Odoo REST API.

Start your headless eCommerce
now.
Find out More
  1. Install Tailwind CSS
  2. Setup React Router
  3. Configuring Odoo API
  4. Building the Category Page Component
  5. Integrating the Component
  6. Running Your Application

Assumption: We’re familiar with the Create React App and you have setup the React Application.

Note: We will set up the project with the name “odoo-category-page.

Step 1: Install Tailwind CSS

You need to implement the Tailwind CSS to effective page UI design.

1. Install the tailwind CSS via npm: This command install the tailwind CSS and Dev depencies. run the following command:

npm install -D tailwindcss postcss autoprefixer

2. Initialize Tailwind CSS: This command creates a configuration file for Tailwind CSS.

npx tailwindcss init -p

3. Configure Tailwind CSS: Open the tailwind.config.js file and Replace the content with the following configuration to set the purge paths for your files:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}", // Adjust if needed
  ],
  theme: {
    extend: {
       colors: {
          primary: "#35979C",
      },
    },
  },
  plugins: [],
}

4. Import Tailwind CSS: In the src directory, open the index.css file and add the following lines to import tailwind’s basecomponents, and utilities styles.

@tailwind base;
@tailwind components;
@tailwind utilities;

Over all setup the project, and you can see the final folder structure.

.
├── src/
│   ├── App.js
│   ├── App.css
│   ├── App.test.js
|   ├── index.js
|   ├── index.css
│   └── logo.svg
├── public/
│   ├── index.html
│   ├── logo.svg
│   ├── robots.txt
│   └── manifest.json
├── package-lock.json
├── package.json
├── tailwind.config.js
└── README.md

Step 2: Setup the React Router

React Router is used to define multiple routes in the application, so you need to install the react router library.

1. Install the React Router: Run the following command in your project directory.

npm install react-router-dom

2. Mount React Router: Open App.js File and replace the code to import BrowserRouterRoute, and Routes for displaying the router component.

import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Layout from "./pages/Layout";

function App() {
  return (
    <>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Layout />}>
            <Route index element={<h1> Render home page </h1>} />
          </Route>
        </Routes>
      </BrowserRouter>
    </>
  );
}

export default App;

After this you can create the layout for category page and other pages like Odoo cart and Odoo product page.

We’ll start by creating a file named Layout.js in the src/pages folder.

import { Outlet } from "react-router-dom";
import Header from "../components/Header";
import Footer from "../components/Footer";

const Layout = () => {
  return (
    <>
      {/* You can add the Header Here */}
      <Header />
      <div className="container max-w-[74%] mx-auto min-h-[60vh]">
        <Outlet />
      </div>
      {/* You can add the Footer Here */}
      <Footer />
    </>
  );
};

export default Layout;

Step 3: Configuring Odoo API

1. Odoo Setup: Ensure you have access to your Odoo instance and the API Authenticate token.

const ODOO_URL = "Odoo_API_URL";
const Authenticate = "AUTH_TOKEN";

2. Create an API Helper: Create a file named index.js to handle API requests in the src/fetcher directory .

const ODOO_URL = "Odoo_API_URL";
const Authenticate = "AUTH_TOKEN";

const callOdooApi = async ({ query, method = "GET", variables }) => {
  try {
    const response = await fetch(`${ODOO_URL}/${query}`, {
      method: method,
      headers: {
        "Content-Type": "application/json",
        Authenticate: Authenticate,
      },
      bosy: JSON.stringify({
        ...(variables && variables),
      }),
    });
    const body = await response.json();

    if (body.errors) {
      throw body.errors[0];
    }
    return {
      status: response.status,
      body,
    };
  } catch (e) {
    console.log(e);
  }
};

export const fetchCategories = async () => {
  const res = await callOdooApi({
    query: "category-list",
    method: "POST",
  });

  const result = res.body;
  return { result };
};

export const getCollectionProducts = async (category) => {
  const handle = {
    filter: { category_url: { eq: category } },
    search: "",
    pageSize: 12,
    currentPage: 1,
  };

  const res = await callOdooApi({
    query: "product-list",
    method: "POST",
    variables: {
      ...handle,
    },
  });
  const result = res.body;

  return { result };
};

Step 4: Building the Category Page Component

1.Create a Category Component: Create a new file Category.js in the src/pages/shop/category.js

import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import { fetchCategories, getCollectionProducts } from "../../../fetcher";

const Category = () => {
  const { categorySlug } = useParams();
  const [products, setProducts] = useState([]);
  const [categories, setCategories] = useState([]);

  useEffect(() => {
    const fetchProducts = async () => {
      const { result: categories } = await fetchCategories();
      setCategories(categories);
      const { result } = await getCollectionProducts(categorySlug);
      setProducts(result);
    };
    fetchProducts();
  }, [categorySlug]);

  const itemList = products?.products;
  const aggregations = products?.aggregations;
  const categoryList = categories?.category;

  return (
    <div class="min-h-screen mt-20  flex">
      <div className="min-w-[20%] p-4 bg-white border-r">
        {Array.isArray(aggregations) &&
          aggregations?.map((aggregation, index) =>
            aggregation?.attribute_code !== "price" ? (
              <div key={index}>
                <h3 class="text-lg font-semibold mb-2">{aggregation?.label}</h3>
                <div class="mb-4">
                  {Array.isArray(aggregation?.options) &&
                    aggregation?.options?.map((option, ind) => (
                      <div class="flex items-center" key={ind}>
                        <input
                          type="checkbox"
                          value={option.value}
                          id={option?.label}
                          class="mr-2"
                        />
                        <label for={option?.label}>{option?.label}</label>
                      </div>
                    ))}
                </div>
              </div>
            ) : (
              <div className="mb-2">
                <h3 class="text-lg font-semibold mb-2">{aggregation?.label}</h3>
                <div class="flex flex-col justify-between items-center">
                  <div class="flex w-full justify-between">
                    <span>${aggregation?.options?.min_price}</span>
                    <span>${aggregation?.options?.max_price}</span>
                  </div>
                  <input
                    type="range"
                    min={aggregation?.options?.min_price}
                    max={aggregation?.options?.max_price}
                    class="w-full mt-2 text-primary bg-primary"
                  />
                </div>
              </div>
            )
          )}
      </div>

      <div class="p-6 max-w-[80%]">
        <div class="flex justify-between items-center mb-4">
          <div>
            <span class="text-sm text-gray-500">
              Products / Default Category / Collections
            </span>
          </div>
          <div class="flex items-center">
            <input
              type="text"
              placeholder="Search..."
              class="border rounded px-2 py-1 mr-4"
            />
            <select class="border bg-gray-100 rounded px-2 py-1 mr-4">
              <option>Public Pricelist</option>
            </select>
            <select class="border rounded px-2 bg-gray-100 py-1">
              <option>Sort By: Featured</option>
              <option>Price: Low to High</option>
              <option>Price: High to Low</option>
            </select>
          </div>
        </div>
        <div class="flex mb-6 overflow-x-scroll py-3 space-x-2">
          {Array.isArray(categoryList) &&
            categoryList?.map((cat, index) => (
              <Link
                to={`/shop/category/${cat.url_key}`}
                key={index}
                class="bg-gray-100 text-sm px-4 py-2 rounded"
              >
                {cat?.name}
              </Link>
            ))}
        </div>

        <div class="grid grid-cols-4 gap-4">
          {itemList?.items &&
            Array.isArray(itemList?.items) &&
            itemList?.items?.map((product, index) => (
              <div key={index} class="bg-white p-4 rounded shadow">
                <img
                  src={product?.thumbnail?.name}
                  alt={product?.name}
                  class="mb-4 w-fit h-60"
                />
                <h4 class="font-semibold text-lg mb-2">{product?.name}</h4>
                <p class="text-green-600 font-bold">
                  ${product?.price_range.minimum_price?.regular_price?.value}
                </p>
              </div>
            ))}
        </div>
      </div>
    </div>
  );
};

export default Category;

Step 5: Integrating the Component

1. Update App.js: Import and render the Category.js component in your App.js.

import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Layout from "./pages/Layout";
import NoPage from "./pages/NoPage";
import Category from "./pages/Shop/Category";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<h1> Render home page </h1>} />
          <Route path="shop/category/:categorySlug" element={<Category />} />
          <Route path="*" element={<NoPage />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

export default App;

Step 6: Running Your Application

1. Start the Development Server: Run the following command to start your React app.

npm run start
Terminal view for starrt react js server

2. View in Browser: Open your browser and navigate to http://localhost:3000/shop/category/desks-1 to see your category page in action.

Final Output of the category page using react js

Conclusion

You’ve successfully built a category page using React.js that fetches data from Odoo! This approach can be further enhanced with features like sorting, and filtering to improve user experience.

We will require the Webkul Odoo REST APIs for headless development services with Webkul and you can also check out our Open Source Odoo React headless eCommerce Application.

Thanks for reading this blog.

Happy Coding!

. . .

Leave a Comment

Your email address will not be published. Required fields are marked*


Be the first to comment.

Back to Top

Message Sent!

If you have more details or questions, you can reply to the received confirmation email.

Back to Home