Quantcast
Channel: Ember.JS - Latest topics
Viewing all articles
Browse latest Browse all 4870

Seeking a patterns to avoid "mutation-after-consumption"

$
0
0

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 :crossed_fingers:t2:

5 posts - 2 participants

Read full topic


Viewing all articles
Browse latest Browse all 4870

Trending Articles