Hello!
I’m seeing the following warning in my app: DEPRECATION: You attempted to update "fields" on "FormComponent", but it had already been used previously in the same computation...
It relates to a form component, which has a @tracked ‘fields’ object in order to keeps tabs on which fields are being rendered into the form, and their status.
Each field has access to the form’s setupField
and validateField
actions. When a field is initiated, it uses setupField
to register with the form. On input, the form then handles validation and tracks the state of each field, and the validation of the form itself (eg, the form is invalid until all registered fields pass their validations).
A basic implementation looks like this:
<Form::Base as |form|>
<form.Body as |body|>
<Fields::String::Edit
@value={{this.email}}
@type="email"
@label="Email Address"
@options={{hash
validators=(array "email" "required")
onSetup=body.setupField
onValidation=body.validateField
}}
/>
</form.Body>
<form.Actions/>
</Form::Base>
An extract of the Form component looks like this:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { isEmpty } from '@ember/utils';
export default class FormComponent extends Component {
@tracked fields = {};
@tracked formIsInvalid = true;
constructor(owner, args) {
super(owner, args);
this.buttonText = this.args.buttonText;
this.fieldValidators = {
// example - evaluates and returns any error messages
required: (inputValue) => {
if (isEmpty(inputValue) || !inputValue) {
return 'Required.';
}
},
};
}
@action
setupField(field) {
const newField = {
previousValue: field.args.value,
errorMessages: [],
isValid: true,
};
this.fields = { ...this.fields, [field.id]: newField };
this.validateField(field);
}
@action
validateField(field, inputValue) {
const { id } = field;
const { fields } = this;
const previousValidity = fields[id].isValid;
let errorMessages = [];
if (field.options && field.options.validators) {
errorMessages = field.options.validators
.map((v) => this.fieldValidators[v](inputValue || field.args.value, field.args.options))
.filter(Boolean);
}
fields[id].errorMessages = errorMessages;
field.errorMessages = fields[id].errorMessages;
fields[id].isValid = fields[id].errorMessages.length === 0;
field.isValid = fields[id].isValid;
this.fields = fields;
this.formIsInvalid = Object.values(this.fields).some((i) => !i.isValid);
if (previousValidity !== field.isValid && this.args.onValidityChange) {
this.args.onValidityChange(this.formIsInvalid);
}
}
}
What I have works but clearly isn’t supported by Ember. I’m looking for any guidance on Octane best-practice patterns, for forms / fields / validations, where I can retain a high level of control over what’s rendered into the form. I also want the form to be able to report its validity to a parent component too - for example, in an instance where the body of the form is rendered into a modal, and I want to be able to tell that component whether or not to display a submit button. this.args.onValidityChange(this.formIsInvalid)
handles that in the above component, but is that going to create the same issue moving forwards?
Cheers