reactive_forms icon indicating copy to clipboard operation
reactive_forms copied to clipboard

FormGroup.valueChanges does not fire when controls change

Open oravecz opened this issue 2 years ago • 2 comments

If I create a StreamBuilder and subscribe to my FormGroup, any changes to controls with that FormGroup does not cause an event to be published to the stream, and thus my components do not refresh.

But if I set my StreamBuilder to use a form control's valueChanges property, it does refresh.

Same goes for stateChanged stream.

How would I go about listening for any stateChanged on my root FormGroup?

As an example - this does not work for me

  @override
  Widget build(BuildContext context) {
    final l10n = PaymentMethodLocalizations.of(context);
    return BlocBuilder<ManualAchCubit, ManualAchState>(
      builder: (context, state) {
        final manualAchCubit = context.read<ManualAchCubit>();
        return StreamBuilder(
          stream: manualAchCubit.form.statusChanged,
          builder: (context, _) {
            return Align(
              alignment: Alignment.centerRight,
              child: SdsButton.primary(
                label: l10n.manualAchFormSubmitCTA,
                semanticLabel: l10n.manualAchFormSubmitCTASemantics,
                onPressed: manualAchCubit.form.valid
                    ? onSubmit
                    : null,
              ),
            );
          },
        );
      },
    );
  }

While this monstrosity does the job (my FormGroup is comprised of individual FormControl variables defined in my Bloc)

  @override
  Widget build(BuildContext context) {
    final l10n = PaymentMethodLocalizations.of(context);
    return BlocBuilder<ManualAchCubit, ManualAchState>(
      builder: (context, state) {
        final manualAchCubit = context.read<ManualAchCubit>();
        return StreamBuilder(
          stream: manualAchCubit.formNickname.statusChanged,
          builder: (context, _) {
            return StreamBuilder(
              stream: manualAchCubit.formRouting.statusChanged,
              builder: (context, _) {
                return StreamBuilder(
                  stream: manualAchCubit.formAccountType.statusChanged,
                  builder: (context, _) {
                    return StreamBuilder(
                      stream: manualAchCubit.formAccountHolder.statusChanged,
                      builder: (context, _) {
                        return StreamBuilder(
                          stream: manualAchCubit
                              .formAccountConfirmation.statusChanged,
                          builder: (context, _) {
                            return StreamBuilder(
                              stream: manualAchCubit
                                  .formRoutingConfirmation.statusChanged,
                              builder: (context, _) {
                                return StreamBuilder(
                                  stream:
                                  manualAchCubit.formAccount.statusChanged,
                                  builder: (context, _) {
                                    return Align(
                                      alignment: Alignment.centerRight,
                                      child: SdsButton.primary(
                                        label: l10n.manualAchFormSubmitCTA,
                                        semanticLabel: l10n
                                            .manualAchFormSubmitCTASemantics,
                                        onPressed: manualAchCubit.form.valid
                                            ? onSubmit
                                            : null,
                                      ),
                                    );
                                  },
                                );
                              },
                            );
                          },
                        );
                      },
                    );
                  },
                );
              },
            );
          },
        );
      },
    );
  }

oravecz avatar Mar 03 '23 20:03 oravecz

Hi @oravecz

Thanks for using Reactive Forms and sorry for the late answer. The first example should do the job just fine. As a matter of fact, a very similar implementation is used in the builtin widget ReactiveStatusListenableBuilder. I recommend to use it instead of a custom implementation.

In addition, I would like to remind you that it will only listen to Status Changes. That means that regardless of how many controls you change if the final status of the Form never changes, then the event will never get triggered.

Let's say you have a Form with all controls as invalid, you will need to make all the controls valid in order to make the Form valid, otherwise changing just the validity of one control will not make the Form validity change because there might be other invalid controls.

joanpablo avatar Aug 13 '23 20:08 joanpablo

Hi @oravecz

Here you can find more info and some examples on how Enable/Disable Submit button

I hope you find it useful.

In that doc you can find another extra widget called ReactiveFormConsumer that can also help to achieve what you need.

joanpablo avatar Aug 13 '23 20:08 joanpablo