Skip to content

feat: add FormState for re-rendering forms after validation - #121

Merged
acoshift merged 1 commit into
masterfrom
feat/form-state
Jun 10, 2026
Merged

feat: add FormState for re-rendering forms after validation#121
acoshift merged 1 commit into
masterfrom
feat/form-state

Conversation

@acoshift

Copy link
Copy Markdown
Member

Summary

Tier-2 website-DX: ctx.FormState() returns a *FormState that holds the submitted form values together with per-field validation errors, so a form can be re-rendered with the user's input after a failed submission (instead of making them retype).

func signup(ctx *hime.Context) error {
    form := ctx.FormState()
    if !strings.Contains(form.Value("email"), "@") {
        form.AddError("email", "Enter a valid email")
    }
    if form.HasErrors() {
        return ctx.View("signup", map[string]any{"Form": form})
    }
    // ... create the account ...
    return ctx.Redirect("/welcome")
}
<input name="email" value="{{.Form.Value "email"}}">
{{if .Form.HasError "email"}}<span class="err">{{.Form.Error "email"}}</span>{{end}}

API

  • Values: Value(name), Values(name), SetValue(name, value) (pre-fill, e.g. an edit form)
  • Errors: AddError(name, msg), Error(name) (first), Errors(name) (all), HasError(name), HasErrors()

Design notes (open to redirection)

  • Handler-passes-to-view, no per-request template funcs — keeps it self-contained (unlike isRoute, which still needs that enabler).
  • Values are copied from the parsed form, so SetValue never mutates ctx.Request.Form (tested).
  • Seeded from query and body (matching ctx.FormValue); uses ParseMultipartForm, so it works for urlencoded and multipart text fields.
  • Categorized, multi-error (map[string][]string) — a field can carry several messages.

Test plan

  • go vet ./...
  • go test ./...100% coverage on all 10 FormState functions (incl. the no-mutation guarantee)
  • go test -race ./...
  • gofmt clean
  • runnable ExampleContext_FormState; CLAUDE.md updated

Tier-2 status

This leaves isRoute as the last Tier-2 item — gated on the per-request template-funcs enabler decision.

🤖 Generated with Claude Code

Tier-2 website-DX: ctx.FormState() returns a *FormState that carries the
submitted form values plus per-field validation errors, so a form can be
re-rendered with the user's input instead of making them retype.

- Value/Values/SetValue — submitted (or pre-filled) values
- AddError/Error/Errors/HasError/HasErrors — per-field validation errors

The handler builds it, adds errors, and passes it to the view on failure;
no per-request template funcs needed. Values are copied from the parsed
form, so SetValue does not mutate the request.

stdlib only, 100% covered. Verified with go vet, go test -race, gofmt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@acoshift
acoshift merged commit a9e847d into master Jun 10, 2026
2 checks passed
@acoshift
acoshift deleted the feat/form-state branch June 10, 2026 01:10
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.

1 participant