Repository navigation
Releases: Randagio13/rapid-form
Release list
v5.0.0
💥 Breaking — field values can now be string[]
Value['value'], FieldError['value'], the value passed to a custom validation, and the values argument to a SchemaResolver widen from string to string | string[].
Do you need to change anything?
Probably not. Reading and rendering values still type-checks:
<span>{values.email?.value}</span>
{values.plan?.value === 'pro' && <ProBadge />}You need to narrow only where a value is passed somewhere that requires a string:
- const trimmed = values.email.value.trim()
+ const raw = values.email.value
+ const trimmed = typeof raw === 'string' ? raw.trim() : raw.join(',')TypeScript flags every such site, so the compiler is your migration checklist — nothing fails at runtime that compiled before.
Custom validations reading .length need no change — it means "non-empty" for both strings and arrays. String-specific methods (test, match, trim, toLowerCase) need a typeof guard.
The bundled Zod and Yup adapters need no changes. They pass values straight to the schema. If you wrote your own resolver and typed its parameter explicitly, widen it to Record<string, string | string[]>, and describe multi-selects as arrays in your schema (z.array(z.string()).min(1)).
Why
<select multiple> and <input type="file" multiple> reported only their first selection — every other selection was unrecoverable from values.
| Case | v4 | v5 |
|---|---|---|
<select multiple>, ts + go selected |
'ts' |
['ts', 'go'] |
| nothing selected | '' |
[] |
| after reset | '' |
[] |
Representing a list requires a list. Joining ('ts,go') or JSON-encoding would keep the type as string but push parsing onto every consumer and be ambiguous against literal values.
Only list-valued fields produce an array. Every other field type still produces a string, and runtime behaviour for single-value fields is unchanged.
<select multiple name="langs" required>
<option value="ts">TS</option>
<option value="go">Go</option>
</select>
// values.langs?.value → ['ts', 'go']A required list-valued field is invalid exactly when the list is empty, and a reset clears it to [] — so a field's value type never changes underneath you.
Also in this release
<input type="file" multiple> reports every file name, rather than just the first. Included here deliberately: it is the same problem, cost one extra branch, and bundling it avoids spending a second major on it later.
What's Changed
💥 Breaking Change
- ✨ feat!: widen field values to
string | string[]for multi-value fields by @Randagio13 in #87
Full Changelog: v4.2.0...v5.0.0
v4.2.0
⚠️ Behavior change — numberOfRequiredFields
The count is now over distinct field names rather than elements, which changes two things:
- A required radio group counts once. Marking a radio group mandatory means putting
requiredon every member, so a three-radio group previously counted as3. If you drive a "N of M complete" indicator offnumberOfRequiredFields, its denominator will drop. - Required elements without a
nameare no longer counted. rapid-form never tracked or validated them, so they produced a denominator no consumer could satisfy.
Nothing else about the count changed — ordinary named fields with required behave exactly as before.
🐛 Fixed — radio groups reported the wrong value in resolver mode
With radios free / pro / max sharing a name, selecting pro reported 'max'. Values were collected by walking form.elements, so every group member wrote to the same key and the last one in DOM order won regardless of which was checked.
values[name] now holds the selected member's value, and '' when nothing is selected.
Per-field mode was already correct and is unchanged — its listener reads the element the user clicked. This only affected forms using a resolver (including the Zod and Yup adapters).
<form ref={(ref) => refValidation(ref, { resolver: zodResolver(schema) })}>
<input type="radio" name="plan" value="free" required />
<input type="radio" name="plan" value="pro" required />
<input type="radio" name="plan" value="max" required />
</form>
// selecting "pro" → values.plan.value === 'pro' (was 'max')Still outstanding
<select multiple> reports only the first selected option, in both modes. Fixing it requires widening Value['value'] to string | string[], which is breaking — tracked in #85 with a costed prototype.
What's Changed
🐛 Bug Fix
- 🐛 fix: resolve radio groups as one field for value and required-count by @Randagio13 in #86
Full Changelog: v4.1.0...v4.2.0
v4.1.0
⚠️ Behavior change — checkbox values
values[name].value for checkbox fields now reports the checked state as 'true' / 'false' instead of 'on'. The same applies to the value passed to a custom validations[name].validation({ value }) function.
Previously, per-field mode stored the checkbox's value attribute, which is 'on' whether checked or not — so the old value carried no information. Resolver mode was already correct; the two modes now agree.
If any code compares a checkbox value against 'on', update it to 'true'. Validation behavior is unchanged — required checkboxes were always validated against their checked state.
✨ New — reading optional fields
refValidation accepts trackUnvalidatedFields to put every named field in values, not just the ones being validated:
<form ref={(ref) => refValidation(ref, { trackUnvalidatedFields: true })}>
<input name="email" type="email" required />
<input name="company" type="text" />
</form>values.company now updates as the user types, while numberOfRequiredFields stays 1 — tracking and requiredness are independent. This replaces the workaround of declaring a placeholder validations entry just to get an optional field registered.
Defaults to false, so existing per-field behavior is unchanged. No effect in resolver mode, which already tracks every named field.
What's Changed
🚀 Enhancement
- ✨ feat: add trackUnvalidatedFields config option by @Randagio13 in #79
🐛 Bug Fix
- 🐛 fix: report checkbox checked state in per-field mode values by @Randagio13 in #81
Full Changelog: v4.0.4...v4.1.0
v4.0.4
What's Changed
Other Changes
- 📦 chore: migrate from tsup to tsdown and update dependencies by @Randagio13 in #77
Full Changelog: v4.0.3...v4.0.4
v4.0.3
What's Changed
Other Changes
- 🐛 fix: wire listeners onto fields mounted after initial render by @Randagio13 in #75
Full Changelog: v4.0.2...v4.0.3
v4.0.2
v4.0.1
What's Changed
🚀 Enhancement
- feat: MutationObserver-based dynamic field tracking by @Randagio13 in #66
📝 Documentation
- docs: update docs and README with dynamic fields and security hardening by @Randagio13 in #68
Full Changelog: v3.3.0...v4.0.1
v4.0.0
What's Changed
🚀 Enhancement
- feat: MutationObserver-based dynamic field tracking by @Randagio13 in #66
📝 Documentation
- docs: update docs and README with dynamic fields and security hardening by @Randagio13 in #68
Full Changelog: v3.3.0...v4.0.0
v3.3.0
What's Changed
🚀 Enhancement
- feat: schema resolver with Zod/Yup adapters by @Randagio13 in #62
Full Changelog: v3.2.0...v3.3.0
v3.2.0
What's Changed
Other Changes
- 🐛 Fix multiple bugs found in code review by @Randagio13 in #53
- 📚 Comprehensive docs overhaul by @Randagio13 in #54
- 🐛 Fix broken CI workflow conditions and add coverage by @Randagio13 in #56
- 🐛 Fix all CI failures by @Randagio13 in #57
- ✨ Add stars badges, fix favicon, rewrite README by @Randagio13 in #58
- 🐛 Fix badges not rendering on docs homepage by @Randagio13 in #59
Full Changelog: v3.1.0...v3.2.0