MrWolfZ/ngrx-forms

Handling Actions with createReducer (NGRX 8)

danielquintero opened this issue · 2 comments

@MrWolfZ thanks for the efforts put into this library!

I am having difficulties handling actions in the new NgRx 8 way together with ngrx-forms. The problem comes when I need to handle a state change coming from an effect that kicks in on an API error and as consequence does markAsUnsubmitted and updateGroup to set errors on the form, and finally dispatching the new form state which the reducer should handle and return the new state.

In principle is working fine, however, I see that when using wrapReducerWithFormStateUpdate to do manual validation the error's that the form should hold are wiped, While debugging I found out this is caused in that last update performed by the validation.

My code:

login.effect.ts

  loginFailure$ = createEffect(() =>
    this._actions$.pipe(
      ofType(loginFailure),
      concatMap(action => of(action).pipe(withLatestFrom(this._loginFacade.form$))),
      map(([error, formState]) => {
        const { code, message } = error;
        const unsubmittedFormState = markAsUnsubmitted(formState);
        const finalFormState = updateGroup<LoginForm>(unsubmittedFormState, {
          [AuthService.RegistrationErrorsAPI[code]]: setErrors({ [code]: true })
        });

        return handleLoginError(finalFormState); // the form state has the error
      })
    )
  );

login.reducer.ts

...

const initialFormState: LoginForm = {
  email: '',
  password: ''
}
export interface LoginState {
  formState: FormGroupState<LoginForm>;
  submittedValue: LoginForm | undefined;
}

const validationFormGroupReducer = updateGroup<LoginForm>({
  email: validate(required, email),
  password: validate(required, minLength(6))
})

const loginReducer = createReducer(
  {
    formState: createFormGroupState<LoginForm>(FORM_ID, initialFormState),
    submittedValue: undefined
  },
  onNgrxForms(),
  on(handleLoginError, (state, action) => {
    console.log('on handleLoginError: ', action);
    return { ...state, formState: { ...action } } // the form state still holds the error
  })
);

export const reducers = wrapReducerWithFormStateUpdate(
  loginReducer,
  (state: LoginState) => {
    console.log('reducers: ', state);
    return state.formState
  },
  validationFormGroupReducer // kicks in as last step in the call chain  
                             // wipes the error from the form
);

export function reducer(s, a) {
  return reducers(s, a)
}

image

image

Yes, this is the expected behavior. If you perform validation inside your form update function it will overwrite any existing errors. However, there is a way to achieve what you want. For this specific case I created the async errors feature. Basically, inside your effect use setAsyncError(code, true) instead of setErrors({ [code]: true }). Async errors are preserved when the validation runs. This also means you will probably have to manually remove the async error with clearAsyncError(code) at some point. Note that async errors are prefix with $ on the state, i.e. if code is USER_NOT_FOUND then errors would be { $USER_NOT_FOUND: true }.

@MrWolfZ sorry I totally overlooked it in the docs. I think we can close this.
setAsyncError is exactly what I was missing.