Writing an Inertia page: render, shared props, deferred work, redirects, and handlers that only gather data.
Pages and Props
Section titled “Pages and Props”Adding a page
Section titled “Adding a page”Write the component:
import { Head } from '@inertiajs/react'
export default function Reports({ rows }: { rows: Row[] }) { return ( <> <Head title="Reports" /> <ul>{rows.map(r => <li key={r.id}>{r.name}</li>)}</ul> </> )}Write the handler:
from sillo_inertia import render
async def reports(request: Request, response: Response): return await render("Reports", {"rows": await Report.all().values()})Register it. Pages are declared as Route objects, with
exclude_from_schema because they are not an API:
Route("/reports", handler=web.reports, methods=["GET"], name="reports", exclude_from_schema=True)No build step, no route file to regenerate, and no client-side router to keep in step.
render without the adapter
Section titled “render without the adapter”render, redirect, back and location are importable on their own. They
find the adapter through the middleware handling the request, so a routes
module builds Inertia responses without importing the module that owns the
application, and so without the circular import that would otherwise cause.
from sillo_inertia import render
async def home(request: Request, response: Response): return await render("Home", {"name": "Sillo"})Outside a request (a background job, a test calling a handler directly) pass one explicitly:
await inertia.render("Home", props, request=request)With neither, render raises OutsideRequestError and names the three ways
to end up there.
A prop can be a value, a callable, or a coroutine function. Callables that want the request take one parameter; those that do not, take none.
{ "count": 5, # a value "total": lambda: Order.count(), # called per request "mine": lambda request: request.user.orders, # given the request "stats": fetch_stats, # async, awaited}Anything a prop returns is serialised to JSON and handed to the browser, so a model instance is the wrong thing to put in one. Return the fields you mean to publish:
{"user": {"id": user.id, "email": user.email}} # explicit{"user": user} # ships every columnListing them by hand is what stops a column added later (a password hash, an internal note) from quietly starting to reach the client.
Shared props
Section titled “Shared props”Props every page receives are registered once, at startup:
inertia.share( app_name=config.app_name, auth=lambda request: {"user": current_user(request)},)They are registered once but resolved per request: a callable prop is called
each time a page renders, so auth reflects whoever is making this request
rather than whoever was making the one during startup.
A page’s own props win on a name clash.
Handlers that only gather data
Section titled “Handlers that only gather data”When a handler does nothing but collect props, @inertia.page takes the rest:
@app.get("/users/{user_id}")@inertia.page("Users/Show")async def show(user_id): return {"user": await User.get(id=user_id)}The function declares only what it uses. Ask for request or response by
name and you get them; leave them out and they are not passed. Path
parameters, injected dependencies and validated bodies all arrive as they
would on any handler.
Returning a response instead of a mapping sends that response untouched, so a handler can still redirect out of a page:
@app.post("/users")@inertia.page("Users/Create")async def create(request: Request): await User.create(**await request.json()) return inertia.redirect("/users")Redirects
Section titled “Redirects”inertia.redirect("/dashboard") # 303 after POST/PUT/PATCH, 302 after GETinertia.back() # to the Refererinertia.back(fallback="/posts") # ...when there is no RefererThe 303 is not decoration. On a 302 the browser repeats the POST against the new URL, so a redirect after a successful create makes a second record.
To send someone out of Inertia entirely (an external URL, a hosted checkout)
use location, which asks the client for a full browser visit rather than an
XHR one:
inertia.location("https://billing.example.com/checkout")Guarding a page
Section titled “Guarding a page”Check and redirect. Do not reach for auth= on the route:
async def dashboard(request: Request, response: Response): if not request.user.is_authenticated: return redirect("/login")
return await render("Dashboard", {...})auth= answers 401, which is right for a JSON API and wrong here:
Inertia’s client surfaces a 401 as an unhandled error modal rather than a
login screen. What the guard should do is application-specific, which is why
it is written out rather than delegated.
Partial reloads and lazy()
Section titled “Partial reloads and lazy()”- Forms and Validation: returning errors from a failed submission
- Assets and Deployment: asset versions and shipping it