Infinite Types Issue in FormApi – Recursive References Causing Type Loops? #1275
-
We've been running into an infinite types issue in FormApi, and I believe the root cause is that FormApi references itself in multiple places—as an example the listeners inside PR: #1261. This creates a recursive type loop (FormApi → Listeners → FormApi, etc.), leading to TypeScript failing to resolve types. We've seen similar issues with Validators and other subtypes, depending on their complexity. A potential long-term solution might be refactoring FormApi to expose only the necessary methods and state for end users. Once a form is defined, do fields or listeners really need access to the full set of defined listeners? Today users can do: validators={{
onChange: ({ value, field }) {
field.form.options.listeners // this is actually a value users can access, even though they realistically never need it.
}
}} Would love to hear thoughts on this—has anyone found a workaround or a better approach to breaking these recursive references in the future? I believe we would have to refactor so that the form object only exposes the methods and state that's needed via an interface that breaks the endless type recurssion! Such as: /**
* Public form API interface
*/
export interface IFormApi<TFormData> {
state: FormState<TFormData>
reset(values?: TFormData, opts?: { keepDefaultValues?: boolean }): void
// ... rest of methods
}
// Then instead of a class use a factory function to implement the FormApi:
export function createForm<TFormData, TSubmitMeta = never>(
options: FormOptions<TFormData, TSubmitMeta> = {},
): IFormApi<TFormData> {
const formApiImpl = new FormApiImpl<TFormData, TSubmitMeta>(options)
formApiImpl.mount()
// Return only the public interface - the properties and methods users should access
return {
// Public state
get state() {
return formApiImpl.state
},
// Public methods
reset: formApiImpl.reset.bind(formApiImpl),
handleSubmit: formApiImpl.handleSubmit.bind(formApiImpl),
getFieldValue: formApiImpl.getFieldValue.bind(formApiImpl),
getFieldMeta: formApiImpl.getFieldMeta.bind(formApiImpl),
setFieldValue: formApiImpl.setFieldValue.bind(formApiImpl),
deleteField: formApiImpl.deleteField.bind(formApiImpl),
pushFieldValue: formApiImpl.pushFieldValue.bind(formApiImpl),
insertFieldValue: formApiImpl.insertFieldValue.bind(formApiImpl),
replaceFieldValue: formApiImpl.replaceFieldValue.bind(formApiImpl),
removeFieldValue: formApiImpl.removeFieldValue.bind(formApiImpl),
swapFieldValues: formApiImpl.swapFieldValues.bind(formApiImpl),
moveFieldValues: formApiImpl.moveFieldValues.bind(formApiImpl),
validate: formApiImpl.validate.bind(formApiImpl),
validateField: formApiImpl.validateField.bind(formApiImpl),
getAllErrors: formApiImpl.getAllErrors.bind(formApiImpl),
}
} Obviously this would be a massive refactor hence why I started a discussion about it, I think this would resolve the problem long term though. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
tested implementing a basic version of this using #1261 as a starting point, still having the same issue: I wonder if this would still regardless be a performance improvement for typescript/overall only expose the APIs that are intended for use. |
Beta Was this translation helpful? Give feedback.
tested implementing a basic version of this using #1261 as a starting point, still having the same issue:
Type instantiation is excessively deep and possibly infinite.ts(2589)
I wonder if this would still regardless be a performance improvement for typescript/overall only expose the APIs that are intended for use.