Skip to content

Support TypedDict form data in on_submit#6301

Merged
masenf merged 22 commits into
reflex-dev:mainfrom
GautamBytes:feat/support-typeddict-forms
Jun 10, 2026
Merged

Support TypedDict form data in on_submit#6301
masenf merged 22 commits into
reflex-dev:mainfrom
GautamBytes:feat/support-typeddict-forms

Conversation

@GautamBytes

@GautamBytes GautamBytes commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • New feature (non-breaking change which adds functionality)

New Feature Submission:

  • Does your submission pass the tests?
  • Have you linted your code locally prior to submission?

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

Description

This PR adds TypedDict support for on_submit form data handlers while keeping existing dict[str, Any] and dict[str, str] handlers working as before.

What changed:

  • allows on_submit handlers annotated with a concrete TypedDict
  • validates required TypedDict keys against statically knowable form fields at form construction time
  • includes both literal name fields and existing id-backed form refs in the validation set
  • skips strict validation when field identifiers are dynamic or the submit chain is opaque, to avoid false positives
  • continues to allow extra form fields
  • keeps the runtime payload shape unchanged

This improves:

  • IDE autocomplete and handler authoring
  • static typing for form payload keys
  • compile-time feedback when a form is missing required fields expected by the handler

closes #6264
fixes ENG-9259

Signed-off-by: Gautam Manchandani <gautammanch@Gautams-MacBook-Air.local>
@codspeed-hq

codspeed-hq Bot commented Apr 8, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing GautamBytes:feat/support-typeddict-forms (4233239) with main (f232cf4)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@greptile-apps

greptile-apps Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds compile-time TypedDict support for on_submit form handlers, enabling typed autocomplete and static field validation while keeping existing dict[str, Any] / dict[str, str] handlers unchanged.

  • Form.create now calls _validate_on_submit_typed_dict_fields which, for TypedDict-annotated handlers, resolves required keys (with a Python 3.10 NotRequired workaround) and raises EventHandlerValueError at app-construction time if any required key has no matching form control.
  • A new on_submit_mapping_event spec (backed by FORM_SUBMIT_MAPPING TypeVar) and a scoped compatibility helper _is_on_submit_mapping_event_arg_compatible_with_typed_dict allow the event type-checking system to accept TypedDict callbacks for on_submit without relaxing the check for other event triggers.
  • _is_form_control = True is added to Radix Slider, Switch, Checkbox, RadioGroup, RadioCards, and SelectRoot so those controls are included in static field discovery.

Confidence Score: 4/5

The change is additive and guarded by well-tested escape hatches; existing submit handlers are unaffected and the new TypedDict path is skipped whenever the form chain is opaque or the form carries an id.

The validation logic and event-type compatibility helper are correct for all documented cases. Two code-clarity observations exist: contracts collected before an opaque event are silently discarded (intentional but undocumented at the return site), and only the first failing contract is surfaced per form. Neither represents wrong runtime behaviour, but they could confuse future maintainers or users debugging multi-handler forms.

forms.py (_validate_on_submit_typed_dict_fields) — the contract-collection loop's early-return and single-error-raise patterns would benefit from inline comments.

Important Files Changed

Filename Overview
packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py Core implementation of TypedDict form validation: adds _validate_on_submit_typed_dict_fields, _get_static_form_field_keys, _get_required_typed_dict_fields, and on_submit_mapping_event. Logic is correct; two minor code-clarity issues noted.
packages/reflex-base/src/reflex_base/event/init.py Adds FORM_SUBMIT_MAPPING TypeVar, on_submit_mapping_event spec, and _is_on_submit_mapping_event_arg_compatible_with_typed_dict helper. Moves BASE_STATE TypeVar under TYPE_CHECKING and converts affected signatures to string annotations. Dead import block at line 2694 (already flagged by previous reviewer).
packages/reflex-base/src/reflex_base/components/component.py Adds _is_form_control: ClassVar[bool] = False as an explicit base-class default on Component; safe change that makes the existing per-class overrides more discoverable.
tests/units/components/forms/test_form.py Comprehensive unit tests covering required/optional fields, id-backed refs, dynamic identifiers, inherited TypedDicts, form-attribute association, partial binding, and error messages.
packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py Adds _is_form_control = True to SliderRoot so Radix primitive sliders are included in static form field discovery.
packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py Adds _is_form_control = True to Slider theme component.
packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py Adds _is_form_control = True to Switch component.
tests/units/components/test_component.py Adds regression test confirming TypedDict relaxation is scoped to on_submit only, not arbitrary mapping-typed event triggers.

Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread packages/reflex-base/src/reflex_base/event/__init__.py
Comment thread packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py Outdated
Comment thread packages/reflex-base/src/reflex_base/event/__init__.py Outdated
@FarhanAliRaza

Copy link
Copy Markdown
Contributor

Can you please add these tests as well and fix the issue?

def test_on_submit_accepts_typed_dict_with_inherited_optional_fields():
    """Inherited optional TypedDict keys should remain optional."""

    class BaseSignupData(TypedDict, total=False):
        nickname: str

    class SignupData(BaseSignupData):
        email: str

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

    form = HTMLForm.create(
        Input.create(name="email"),
        on_submit=SignupState.on_submit,
    )

    assert isinstance(form.event_triggers["on_submit"], EventChain)
def test_on_submit_accepts_controls_associated_via_form_attribute():
    """Controls associated via the HTML form attribute should not fail validation."""

    class SignupData(TypedDict):
        email: str

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

    form = HTMLForm.create(
        id="signup",
        on_submit=SignupState.on_submit,
    )
    Input.create(name="email", form="signup")

    assert isinstance(form.event_triggers["on_submit"], EventChain)

FarhanAliRaza and others added 7 commits April 16, 2026 16:13
Use __required_keys__ for inherited TypedDict optional fields, skip
validation for forms with id (HTML form attribute), replace stringly-typed
control detection with _is_form_control marker, use concrete Mapping type
in event spec, narrow exception handling, and add integration tests.
On 3.10, typing.TypedDict ignores typing_extensions.NotRequired when
populating __required_keys__. Fall back to annotation inspection to
subtract NotRequired fields on older Python versions.
Unit tests now pair happy paths with failing counterparts to prove
validation is active. Integration test carries input/expected data
per variant instead of fragile runtime app_source detection.
FarhanAliRaza
FarhanAliRaza previously approved these changes Apr 16, 2026
Comment thread packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py Outdated

@masenf masenf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update from main.

this is a nice feature. i think the new iter_form_fields functionality could be used to replace _get_all_refs in the Form implementation which is the only place where that is really used. We could get rid of that relatively shaky method.

will also have to make sure this continues to work when the form's children are memoized components (have event handlers or other state associated with them). update the integration test to make sure at least one of the fields depends on state and thus will get auto-memoized.

@masenf masenf requested a review from a team as a code owner May 10, 2026 20:08
@masenf

masenf commented May 11, 2026

Copy link
Copy Markdown
Collaborator

@greptile-apps re-review this pr

masenf and others added 3 commits May 11, 2026 11:19
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
FarhanAliRaza
FarhanAliRaza previously approved these changes May 26, 2026
@masenf masenf requested a review from Alek99 as a code owner June 4, 2026 22:23
@masenf

masenf commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

This is basically ready to go, but because of the in-flight min-dep issue in pyproject.toml, i'll be submitting a followup PR to relax the must-be-on-pypi requirement for deps that have a .dev component in them.

Then we'll have another CI update that fails to publish final releases if any pins are still at .dev versions

@masenf masenf merged commit 2f95065 into reflex-dev:main Jun 10, 2026
107 checks passed
@GautamBytes GautamBytes deleted the feat/support-typeddict-forms branch June 10, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

on_submit should support TypedDict for form data

3 participants