joanpablo / reactive_forms

This is a model-driven approach to handling form inputs and validations, heavily inspired in Angular's Reactive Forms
MIT License
469 stars 86 forks source link

Error: ReactiveFormField widget couldn't find a parent widget. An instance of ReactiveTextField<dynamic> widget must be under a ReactiveForm or a ReactiveFormArray in the widgets tree. #101

Closed jbt-cii closed 3 years ago

jbt-cii commented 3 years ago

Hello,

Since yesterday, I try to use this great package with the configuration explained in that closed issue: https://github.com/joanpablo/reactive_forms/issues/99

Today I have the following error:

ReactiveFormField widget couldn't find a parent widget. An instance of ReactiveTextField<dynamic> widget must be under a ReactiveForm or a ReactiveFormArray in the widgets tree.

I began recently to develop in Flutter so maybe my questions are basics. And sorry if my sentences are not very good I'm French :-)

I try to implement the method explained in this example: https://github.com/joanpablo/reactive_forms/blob/master/example/lib/samples/simple_sample.dart

In my page (which extends StatefulWidget) and in the class "State" I declared:

  FormGroup get form => fb.group(<String, dynamic>{
        'name': ['', Validators.required],
        'sendNotifications': [false, Validators.required],
      });

After, I use "Cubit" (from BLOC):


...
  child: Column(
    children: [
      BlocConsumer<ComponentCubit, ComponentState>(
        listener: (context, state) {
          if (state is ComponentError) {
            widgetBuildError(context, state.message);
          }
        },
        builder: (context, state) {
          if (state is ComponentInitial) {
            return widgetBuildInitial(context, form);
          } else if (state is ComponentLoading) {
            return widgetBuildLoading();
          } else if (state is ComponentLoaded) {
            return widgetBuildLoaded(context);
          } else {
            // In case of error we call the initial widget here and we handle the error with the above listener
            return widgetBuildInitial(context, form);
          }
        },
      )
    ],
  ),
...

And for the Cubit initial part I declared a widget like this:

  Widget widgetBuildInitial(context, form) {
    return ReactiveFormBuilder(
      form: () => form,
      builder: (context, form, child) {
        return Column(
          children: [
            myWidgetForm(context, form),
            ReactiveFormConsumer(
              builder: (context, form, child) {
                return GestureDetector(
                  child: myWidgetValidateButton(context, form, null),
                  onTap: () => myFormSubmit(context, form),
                );
              },
            ),
          ],
        );
      },
    );
  }

=> I declared a "GestureDetector" widget because I separated the code into several parts and I did not find a way to manage the action :-)

My function "myFormSubmit":

  void myFormSubmit(context, form) {
    if (form.valid) {
      this._myFields01 = form.control("my_field_01");
      this._myFields02 = form.control("my_field_02");
      this._myFields03 = form.control("my_field_03");
      final myObjectCubit = context.bloc<ComponentCubit>();
      myObjectCubit.mySpecificMethodCubit(this._myFields01, this._myFields02, this._myFields03);
    }

=> So here I tried to pass by parameters the validated content of the fields

To manage the "input" fields I declared a generic global class:

...
import 'package:reactive_forms/reactive_forms.dart';
...

class FormInputText extends StatefulWidget {
  final Function(int, String) onChangedStep;
  final String fieldName;
  final String fieldTitle;
...

  const FormInputText({
    Key key,
    @required this.fieldName,
    @required this.fieldTitle,
...
    this.onChangedStep,
  }) : super(key: key);

  @override
  _FormInputTextState createState() {
    return _FormInputTextState();
  }
}

class _FormInputTextState extends State<FormInputText> {
  String _fieldName = "";
  String _fieldTitle = "";
...

  @override
  Widget build(BuildContext context) {
    this._fieldName = widget.fieldName;
    this._fieldTitle = widget.fieldTitle;
...
    return Container(
      height: 60.0,
      child: Row(
        children: [
          Expanded(
            child: ReactiveTextField(
              formControlName: this._fieldName,
              style: TextStyle(
                color: Theme.of(context).accentColor,
                ...
              ),
              decoration: InputDecoration(
                contentPadding: EdgeInsets.all(0.0),
                ...
              ),
            ),
          ),
        ],
      ),
    );
  }
}

And I call this above generic class like this:

Widget myWidgetForm(context, form) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: [
      FormInputText(
        fieldName: "my_field_01",
        fieldTitle: "Field 01",
      ),
      SizedBox(height: 2.0),
      FormInputText(
        fieldName: "my_field_02",
        fieldTitle: "Field 02",
      ),
      SizedBox(height: 2.0),
      FormInputText(
        fieldName: "my_field_03",
        fieldTitle: "Field 03",
      ),
    ],
  );
}

So now with the error I get I think I should use "ReactiveForm" instead of "ReactiveFormBuilder" like this:

...
    child: ReactiveForm(
      formGroup: this.form,
      child: Column(
        children: [
          BlocConsumer<ComponentCubit, ComponentState>(
            listener: (context, state) {
              if (state is ComponentError) {
                widgetBuildError(context, state.message);
              }
            },
            builder: (context, state) {
              if (state is ComponentInitial) {
                return widgetBuildInitial(context, form);
              } else if (state is ComponentLoading) {
                return widgetBuildLoading();
              } else if (state is ComponentLoaded) {
                return widgetBuildLoaded(context);
              } else {
                // In case of error we call the initial widget here and we handle the error with the above listener
                return widgetBuildInitial(context, form);
              }
            },
          )
        ],
      ),
    ),
...

But I don't understand why your example doesn't show a "ReactiveForm" at all: https://github.com/joanpablo/reactive_forms/blob/master/example/lib/samples/simple_sample.dart

jbt-cii commented 3 years ago

I tried what I said before... using directly "ReactiveForm()" does not work either. Maybe because the final "ReactiveTextField" are inside StatefulWidget (in the "State" part). The FormGroup is declared outside (elsewhere higher in the widget tree). Did you try such a usage before?

jbt-cii commented 3 years ago

OK I had a problem in my declaration of "FormGroup". I forgot to declare the good fields here:

  FormGroup get form => fb.group(<String, dynamic>{
        'name': ['', Validators.required],
        'sendNotifications': [false, Validators.required],
      });

Better with:

  FormGroup get form => fb.group(<String, dynamic>{
        'my_field_01': ['', Validators.required],
        'my_field_02': ['', Validators.required],
        'my_field_03': ['', Validators.required],
        'sendNotifications': [false, Validators.required],
      });

I close this issue :-)

joanpablo commented 3 years ago

Hi @jbt-cii,

Thanks for using Reactive Forms, I'm glad that you have solved the issue. Don't hesitate to open another if you have more questions.

Best regards

jbt-cii commented 3 years ago

Thanks for your answer. Yes I have another question so I have just opened another issue: https://github.com/joanpablo/reactive_forms/issues/104

:-)