Skip to content

on_submit should support TypedDict for form data #6264

@masenf

Description

@masenf

Describe the Enhancement you want
Currently, on_submit handlers take a form_data argument typed as dict[str, Any]. Users should be able to specify a TypedDict for better IDE integration and doc/schema generation for machine consumption.

With this information it should also be possible to determine at compile time if the form contains the fields necessary to satisfy the type and raise a helpful error: Form is missing field "fname" expected by handler MyState.on_submit

Which feature do you want to improve? (and what problem does it have)

Form.on_submit is loosely typed

What is the benefit of the enhancement?

  • Better type checking
  • Improved IDE hinting
  • Better context for schema generation

Show an example/usecase were the improvement are needed.

Before (untyped):

import reflex as rx

class SignupState(rx.State):
    def on_submit(self, form_data: dict[str, Any]):
        # No IDE hints, no idea what keys exist
        name = form_data["name"]       # typo? missing field? no way to know
        email = form_data["email"]
        pasword = form_data["pasword"]  # silent bug — misspelled key

def signup_form():
    return rx.form(
        rx.input(name="name", placeholder="Name"),
        rx.input(name="email", placeholder="Email"),
        rx.input(name="password", type="password", placeholder="Password"),
        on_submit=SignupState.on_submit,
    )

After (typed with TypedDict):

import reflex as rx
from typing import TypedDict

class SignupData(TypedDict):
    name: str
    email: str
    password: str

class SignupState(rx.State):
    def on_submit(self, form_data: SignupData):
        # IDE autocompletes form_data.name, form_data.email, form_data.password
        name = form_data["name"]
        email = form_data["email"]
        password = form_data["password"]
        # form_data["pasword"]  # IDE/mypy flags this immediately

def signup_form():
    return rx.form(
        rx.input(name="name", placeholder="Name"),
        rx.input(name="email", placeholder="Email"),
        rx.input(name="password", type="password", placeholder="Password"),
        on_submit=SignupState.on_submit,
    )

Compile-time validation — missing field example:

class SignupData(TypedDict):
    fname: str
    lname: str
    email: str

class SignupState(rx.State):
    def on_submit(self, form_data: SignupData):
        ...

def signup_form():
    return rx.el.form(
        rx.el.input(name="email", placeholder="Email"),
        # "fname" and "lname" inputs are missing!
        on_submit=SignupState.on_submit,
    )

Expected compile-time error output:

$ reflex run

──── Compile Error ─────────────────────────────────────────────────────────────

Form field mismatch in signup_form (app/pages/signup.py:14)

  The on_submit handler `SignupState.on_submit` expects form data matching
  `SignupData` with fields: fname, lname, email

  Fields missing from form inputs:
    ✗ "fname" — no <input name="fname"> found in form
    ✗ "lname" — no <input name="lname"> found in form

  Fields present:
    ✓ "email"

  Hint: Add the missing input elements to your form, or remove the fields
  from `SignupData` if they are optional.

────────────────────────────────────────────────────────────────────────────────

The typed version gives you three wins at once: IDE autocomplete on the dict keys, mypy/pyright catching typos in handler code, and Reflex itself validating at compile time that the form's name attributes match what the handler expects. The dict[str, Any] signature would continue to work as-is for anyone who doesn't need the stricter checking.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementAnything you want improved

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions