MrWolfZ/ngrx-forms

Error: action value wrapped twice wrapped with 'value' when updating form value.

Closed this issue · 3 comments

Hi there.

I am trying to update a form value with a nested form using ngrx forms.

When changing a value through the UI, an event is fired automatically with NGRX forms. The problem is that somewhere the library is wrapping the value that I want to use twice. See the image that my value is the array, but it is wrapped twice.
image

This then breaks when it hits the following code in the library.
image

The error code is here
image

In my project, the components are arranged that there is a parent component that gets the form state from the store. It then passes down the form State through an Input() to the child component, which is not in contact with the store.

I'm using this in a MatSelect with multiple selections enabled.

This is the HTML for the CHILD component

<form *ngIf="tableFormGroup"
      [ngrxFormState]="tableFormGroup"
<mat-form-field appearance="standard" class="type no-underline">
              <mat-label>{{ 'ibctable.type.label' | translate }}</mat-label>
              <mat-select [ngrxFormControlState]="tableFormGroup.controls.type" multiple>
                <mat-select-trigger>
                  {{tableFormGroup.controls.type.value ? tableFormGroup.controls.type[0] : ''}}
                  <span *ngIf="tableFormGroup.controls.type.value?.length > 1">
                    (+{{tableFormGroup.controls.type.value.length - 1}} {{tableFormGroup.controls.type.value?.length === 2 ? 'other' : 'others'}})
                  </span>
                </mat-select-trigger>
                <mat-option *ngFor="let option of filterHeaders.IBCType" [value]="option.id">{{option.name}}</mat-option>
              </mat-select>
            </mat-form-field>
    </form>

The Typescript code for the CHILD

export class IOIbcTableComponent implements OnInit, AfterViewInit, OnChanges {

  public dataSource!: MatTableDataSource<IbcEntry>;

  @Input() tableFormGroup?: FormGroupState<IIbcTableFilterFormValue>;

}

The HTML for the PARENT component

<div>
  <io-ibc-table
    [show]="(tableState$ | async) === 'HAS_DATA'"
    [entrys]="(entrysState$ | async)!"
    [tableFormGroup]="(formState$ | async)!['controls']!.table"
    [filterHeaders]="(tableFilterHeadersState$ | async)"
  ></io-ibc-table>
</div>

The Typescript code for the PARENT

export class OverViewPageComponent implements OnInit {

    constructor(
    private store$: Store<AppState>,
  ) {
  }

  ngOnInit(): void {
    this.tableState$ = this.store$.select(s => s.overviewPage.tableState );
    this.tableFilterHeadersState$ = this.store$.select(s => s.overviewPage.tableFilterHeaders );
  }

}

I maybe this is something I have not implemented correctly or a problem with NGRX forms. In any case, a fix or something to detect this error would be handy.

Let me know if I can provide any more details.

Thanks

Andrew

This is now Fixed. It was my fault for not implementing the 'Boxed' type on the array of string values relating to the Select component options.

I think a more descriptive warning message in this area would help other users who run into this problem.

Thanks

I'm always happy to improve the error messages. However, I am still not quite sure what is happening in your case.

So with the mat-select you want to be able to select an array of objects as the value, right? And the value that you want to select is something like this: type TypeControlValue = Array<{ id: number; name: string }>? Then the type of tableFormGroup.controls.type would be FormControlState<Boxed<TypeControlValue>>. Can you please post the content of IIbcTableFilterFormValue so that I can validate this assumption?

From the error it seems that instead of the form control reducer, which is supposed to handle boxed values, the form array reducer got called. That reducer cannot handle boxed values, which leads to the error you see. However, I am not sure how that can happen, since the only way the array reducer gets called is if during initialization of the form the value of type was an array instead of a boxed array, which would be a compile error. Let's have a look at an example:

interface Entry {
  id: number;
}

interface FormValue {
  entries: Boxed<Entry[]>;
}

const FORM_ID = 'array';

// this initial value would cause the runtime error you saw, but it should also cause
// a compile time error
const INITIAL_STATE_ERROR = createFormGroupState<FormValue>(FORM_ID, {
  entries: [],
});

// this is the correct code
const INITIAL_STATE = createFormGroupState<FormValue>(FORM_ID, {
  entries: box([]),
});

Can you please also post how you are initializing the form? Before I implement a new error message I want to make sure that it is possible to trigger this error with valid code.

closed due to inactivity