Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cold-singers-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect-app/vue-components": patch
---

fix: enhance field registration and error handling for better component re-mounting
13 changes: 0 additions & 13 deletions packages/vue-components/src/components/OmegaForm/OmegaInput.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<template>
<component
:is="form.Field"
:key="fieldKey"
:name="name"
:validators="{
onChange: schema,
Expand Down Expand Up @@ -82,18 +81,6 @@ const meta = computed(() => {
return props.form.meta[propsName.value]
})

// Key to force Field re-mount when meta type changes (for TaggedUnion support)
const fieldKey = computed(() => {
const m = meta.value
if (!m) return propsName.value
// Include type and key constraints in the key so Field re-mounts when validation rules change
// Cast to any since not all FieldMeta variants have these properties
const fm = m as any
return `${propsName.value}-${fm.type}-${fm.minLength ?? ""}-${fm.maxLength ?? ""}-${fm.minimum ?? ""}-${
fm.maximum ?? ""
}`
})

// Call useIntl during setup to avoid issues when computed re-evaluates
const { trans } = useIntl()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ props.register(computed(() => ({ name: props.field.name, label: props.label, id
// This ensures errors persist when Field components re-mount due to :key changes
const _errors = computed(() => {
const fieldMeta = formFieldMeta.value[props.field.name] as any
// Treat errors as an array (like useOmegaForm does)
return fieldMeta?.errors ?? []
})
const errors = computed(() =>
Expand Down
14 changes: 12 additions & 2 deletions packages/vue-components/src/components/OmegaForm/useOmegaForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -962,8 +962,18 @@ export const useOmegaForm = <
// /** @experimental */
handleSubmitEffect,
registerField: (field: ComputedRef<{ name: string; label: string; id: string }>) => {
watch(field, (f) => fieldMap.value.set(f.name, { label: f.label, id: f.id }), { immediate: true })
onUnmounted(() => fieldMap.value.delete(field.value.name)) // todo; perhap only when owned (id match)
watch(field, (f) => {
fieldMap.value.set(f.name, { label: f.label, id: f.id })
}, { immediate: true })
onUnmounted(() => {
// Only delete if we still own this entry (id matches)
// This prevents old components from deleting entries registered by new components
// during re-mount transitions (e.g., when :key changes)
const currentEntry = fieldMap.value.get(field.value.name)
if (currentEntry?.id === field.value.id) {
fieldMap.value.delete(field.value.name)
}
})
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ const Key = Symbol("injected") as InjectionKey<Map<string, { label: string; id:

export const useRegisterField = (field: ComputedRef<{ name: string; label: string; id: string }>) => {
const map = injectCertain(Key)
watch(field, (f) => map.set(f.name, { label: f.label, id: f.id }), { immediate: true })
watch(field, (f) => {
map.set(f.name, { label: f.label, id: f.id })
}, { immediate: true })
onUnmounted(() => map.delete(field.value.name)) // todo; perhap only when owned
}

Expand Down