Usage & Best Practices
You know the syntax. You know how to render a div. But how do you architect a scalable, maintainable application using Probo UI?
Because Probo brings UI generation into pure Python, it is easy to accidentally mix backend business logic with frontend rendering. This guide outlines the golden rules and best practices for building enterprise-grade Probo applications.
1. Separation of Concerns (Views vs. Components)
The Rule: Your HTTP routing views should never contain complex DOM trees.
Just like in django where you separate settings/configs from rest of project, you should keep your Probo UI components in dedicated files (e.g., components/,pages/) and simply import them into your router/views.
❌ Anti-Pattern (The Messy View)
from django.http import HttpResponse
from probo import div, h1, p, table, tr, td
def user_dashboard(request):
# Mixing DB logic with a massive UI tree
users = User.objects.all()
ui = div(
h1("Dashboard"),
p("Welcome back!"),
table(*[tr(td(u.name), td(u.email)) for u in users], Class="table"),
id="main-dash"
)
return HttpResponse(ui)
✅ Best Practice (The Probo Way)
- Organize your project like this:
myproject/
├── components/
│ └── dashboard.py
├── pages/
│ ├── base.py
│ └── home.py
├── views.py
# rest of structure
component layer
- components/dashboard.py
from probo import div, h1, p, table, tr, td
def dashboard_ui(users):
"""Pure presentational component."""
return div(
h1("Dashboard"),
p("Welcome back!"),
table(*[tr(td(u.name), td(u.email)) for u in users], Class="table"),
id="main-dash"
)
page layer
- pages/home.py
!! tip:
- use a base.py as inheritance for rest of pages
- when accessing derect document (DOCTYPE object) access the "html" object via html_doc
# pages/base.py
from probo.templates import base_template_tree
def base_page(*children):
"""Reusable base template"""
base = base_template_tree(override_body=True, override_head=True)
body = base.html_doc.find(lambda n: n.tag == "BODY")
if body:
body.add(*children)
return base
from probo import div, h1, p, table, tr, td
from myproject.myapp.pages import base_template
from myproject.myapp.components import dashboard_ui
def order_page(*args,**kwargs):
users = kwargs.get('users',[])
dashboard = dashboard_ui(users)
base = base_template(dashboard)
title_obj =base.html_doc.find(lambda n:n.tag == 'TITLE')
if title_obj:
title_obj.inner_html('dashboard page')
return base
views.py
!! tip:
- if you are using a base.html just import the components directly intop a container then pass it ascontext via render otherwise use Http class.
from django.http import HttpResponse
from myproject.myapp.pages import order_page
def user_dashboard(request):
# 1. Fetch Data
users = User.objects.all()
# 2. Pass to Component
return HttpResponse(order_page(users=users).render())
2. Managing State & Context
When dealing with deep component trees, passing standard variables (prop-drilling) through 10 layers of functions becomes exhausting.
✅ Best Practice: Use ProboContextProvider
Instead of passing the request or session down manually, push it into Probo's thread-safe global context at the very top of your view.
from myproject.context import ProboContextProvider as Context
from components.navbar import navbar_ui
def my_django_view(request):
# Inject the session/user globally for this specific request thread
with Context() as ctx:
ctx.push(user=request.user, theme=request.session.get("theme", "light"))
# navbar_ui can now call Context.get("user") directly!
return HttpResponse(navbar_ui().render())
3. Choosing the Right Architecture
Probo offers multiple ways to build UI. Choosing the wrong one for your specific task will lead to unnecessary memory overhead or code verbosity.
| Scenario | Recommended Paradigm | Why? |
|---|---|---|
| Standard Layouts / Forms | Heavy Functional (div, p) | Fast, clean syntax. Eagerly evaluates to strings. |
| Dynamic Mutation | Heavy OOP (DIV, P) | Allows you to use .modify(), style_manager, and attr_manager before calling .render(). |
| Massive Data Tables | Light Tags (l_div, l_tr) | Shares a single memory pipeline. Prevents RAM spikes when rendering 10,000+ elements. |
| Deeply Nested Pages | ProboFunctionalExecuter DSL | Keeps your Python code perfectly flat (no indentation hell) using + and / operators. |
| .html files | ProboTemplateParser | Parses your templates into SSDOM (Server-Side Document Object Model) consisting of Light/Heavy OOP trees. |
4. Streaming Massive Payloads
If your UI tree contains thousands of elements (like a massive report or data grid), never use .render(). .render() forces the server to concatenate the entire string or buid list/deque in memory before sending it to the user.
✅ Best Practice: O(1) Memory Streaming
Always use .stream() paired with a streaming HTTP response (like Django's StreamingHttpResponse or FastAPI's StreamingResponse).
# in myproject/myapp/components/streamed_component.py
from probo import table, tr, td
def data_report(*args,**kwargs):
# Assuming 'get_millions_of_rows()' is a Python generator
lazy_rows = (tr(td(row.id), td(row.data)) for row in get_millions_of_rows())
report_ui = table(lazy_rows, style="table-layout: fixed; width: 100%;")
return report_ui
# in myproject/myapp/views
from django.http import StreamingHttpResponse
from myproject.myapp.components import data_report
def massive_report_view(request):
report_ui = data_report()
# Stream chunks of 50 elements instantly to the browser!
return StreamingHttpResponse(report_ui.stream(batch=100))
5. Security: Trusting Raw HTML
Probo automatically escapes all strings passed into components to prevent Cross-Site Scripting (XSS) attacks.
If you are fetching raw, pre-rendered HTML from a trusted source (like a Markdown-to-HTML database column) and want Probo to render it as actual HTML instead of escaped text, you must explicitly tell Probo it is safe.
✅ Best Practice: The Escape Hatch
from probo import div
from probo.shortcuts import raw
def blog_post(post):
return div(
h1(post.title),
# raw() marks the string as safe, preventing auto-escaping
div(raw(post.html_content), Class="prose")
)
# OR
from probo import div
from probo.utility import ProboSourceString
def blog_post(post):
return div(
h1(post.title),
# raw() marks the string as safe, preventing auto-escaping
div(ProboSourceString(post.html_content), Class="prose")
)
Quick Tips
- Keep components small and focused (single responsibility).
- Use base_page() for consistent layout across your app.
- Prefer functional style unless you need deep mutation.
- Use Context instead of passing request everywhere.
- Always stream large responses.