reactive_forms icon indicating copy to clipboard operation
reactive_forms copied to clipboard

If field is not visible reset value of that field

Open ShivamMattoo33 opened this issue 2 years ago • 5 comments

Hello i have just started using the reactive forms package and stumbled upon an odd behaviour

i have two reactive text fields lets say A and B Text field B only shows when text field A value is say ‘Yes’

so when i enter Yes in Text field A Text field B shows up and then I enter ‘hello’ in text field B Now if i change the value of Text field A to No it hides the Text field B but the value ‘hello’ remains there when i do form.value

how do i have the value of Text Field B as null if its not showing on screen anymore

thanks

ShivamMattoo33 avatar Jun 01 '23 13:06 ShivamMattoo33

Hi you can achieve this by clearing the field or setting the field to disabled. If you set the field to disabled you will not get the value when you call form.value but if you enable it again then it will keep the value

Ch4rl3B avatar Jun 01 '23 17:06 Ch4rl3B

How do i set the field to disabled when access to disabled is in the Form group section and at that point i dont have access to form.control(‘name’) and I cant disable in the actual reactive textfield because it only takes either FormControlName or FormControl

ShivamMattoo33 avatar Jun 01 '23 17:06 ShivamMattoo33

Code sometimes is better than talk 😅

class ExampleForm extends StatelessWidget {
  final _form = FormGroup({
    'a': FormControl<String>(),
    'b': FormControl<String>(disabled: true),
  });

  ExampleForm({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ReactiveForm(
      formGroup: _form,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ReactiveTextField(
            key: const Key('a'),
            formControlName: 'a',
            onChanged: (FormControl<String> control) {
              if (control.value.isEmptyOrNull) return;
              if (control.value?.trim().toLowerCase() == 'yes') {
                _form.control('b').markAsEnabled();
                _form.markAsTouched();
              } else {
                _form.control('b').markAsDisabled();
                _form.markAsTouched();
              }
            },
          ),
          const SizedBox(
            height: 16,
          ),
          ReactiveFormConsumer(builder: (context, form, _) {
            if (form.control('b').enabled) {
              return ReactiveTextField(
                key: const Key('b'),
                formControlName: 'b',
              );
            }
            return Container();
          }),
          const SizedBox(
            height: 16,
          ),
          ElevatedButton(
            onPressed: () {
              toastLong(_form.value.toString());
            },
            child: const Text('Submit'),
          )
        ],
      ),
    );
  }
}

Hope this help you 👍🏼

Ch4rl3B avatar Jun 01 '23 17:06 Ch4rl3B

This hack works for sure but it feels dirty. Look at the code and things I have to add to make it work. Isnt there a simple way where if the code doesnt satisfy the if condition and is not on the screen. Its value and validation becomes null

 return ReactiveFormBuilder(
      form: () => fb.group(<String, Object>{
        'email': FormControl<String>(
          validators: [Validators.required, Validators.email],
        ),
        'input': FormControl<String>(
          validators: [Validators.required],
          disabled: true,
        ),
        'radio': FormControl<String>(
          validators: [Validators.required],
          disabled: true,
        ),
        'password': ['', Validators.required, Validators.minLength(8)],
        'rememberMe': false,
      }),
      builder: (context, form, child) => ReactiveFormConsumer(
        builder: (context, form, child) => BoltScreenWrapper(
          titleText: 'Login sample',
          builder: (context) => Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              BoltTextFieldReactive(
                formControlName: 'email',
                onChanged: (control) {
                  if (control.value == null || control.value == "") {
                    return;
                  }
                  if (control.value == '[email protected]') {
                    form.control('input').markAsEnabled();
                  } else if (control.value == '[email protected]' && form.control('input').value == 'Honda') {
                    form.control('radio').markAsEnabled();
                  } else {
                    form.control('input').markAsDisabled();
                    form.control('radio').markAsDisabled();
                  }
                },
                validationMessages: {
                  ValidationMessage.required: (_) => 'The email must not be empty',
                  ValidationMessage.email: (_) => 'The email value must be a valid email',
                  'unique': (_) => 'This email is already in use',
                },
                labelText: 'Email',
              ),
              const SizedBox(height: 26.0),
              if (form.control('email').value == '[email protected]')
                BoltTypeAheadFieldReactive<String>(
                  labelText: 'Vehicles',
                  formControlName: 'input',
                  validationMessages: {
                    ValidationMessage.required: (_) => 'The field must not be empty',
                  },
                  viewDataTypeFromTextEditingValue: (control) {
                    if (control == "") {
                      return control;
                    }
                    if (control == 'Honda' && form.control('email').value == '[email protected]') {
                      form.control('radio').markAsEnabled();
                      return control;
                    } else {
                      form.control('radio').markAsDisabled();
                      return control;
                    }
                  },
                  hintText: 'Enter a make',
                  optionsBuilder: (TextEditingValue textEditingValue) =>
                      makes.where((element) => element.contains(textEditingValue.text)),
                  maxLength: 48,
                ),
              const SizedBox(height: 136.0),
              if (form.control('email').value == '[email protected]' && form.control('input').value == 'Honda')
                BoltRadioGroupReactive<String>(
                  formControlName: 'radio',
                  labelText: 'Location Type',
                  errorText: 'This is error',
                  items: locationTypeItems
                      .map((locationTypeItem) => BoltRadioItem<String>(
                          titleText: locationTypeItem, value: locationTypeItem, subtitleText: 'Papi'))
                      .toList(),
                ),

ShivamMattoo33 avatar Jun 01 '23 19:06 ShivamMattoo33

Hi @ShivamMattoo33,

It is highly recommended to use Reactive Forms and Flutter with a state management library, it is a good way to separate responsibilities and remove business logic from UI rendering logic.

There are several approaches to achieving what you are asking for. This is just another approach without a state management library, but it's not the only one.

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

class ShowHideSample extends StatefulWidget {
  @override
  State<ShowHideSample> createState() => _ShowHideSampleState();
}

class _ShowHideSampleState extends State<ShowHideSample> {
  FormGroup form = fb.group({
    'email': FormControl<String>(),
    'input': FormControl<String>(disabled: true),
    'radio': FormControl<String>(disabled: true)
  });

  FormControl<String> get email => form.control('email') as FormControl<String>;

  FormControl<String> get input => form.control('input') as FormControl<String>;

  FormControl<String> get radio => form.control('radio') as FormControl<String>;

  bool get isInputVisible {
    return email.value?.trim() == '[email protected]';
  }

  bool get isRadioVisible {
    return input.enabled && input.value?.trim() == 'Honda';
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ReactiveForm(
        formGroup: form,
        child: Column(
          children: [
            ReactiveTextField(
              formControl: email,
              decoration: const InputDecoration(enabled: false),
            ),
            ReactiveValueListenableBuilder(
              formControl: email,
              builder: (context, control, child) {
                if (isInputVisible) {
                  input.markAsEnabled();
                  return child!;
                } else {
                  input.reset(disabled: true);
                  return Container();
                }
              },
              child: ReactiveTextField(
                formControl: input,
              ),
            ),
            ReactiveValueListenableBuilder(
              formControl: input,
              builder: (context, control, child) {
                if (isRadioVisible) {
                  radio.markAsEnabled();
                  return child!;
                } else {
                  radio.reset(disabled: true);
                  return Container();
                }
              },
              child: ReactiveTextField(
                formControl: radio,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

using a state management library allows you to move the FormGroup definition, field access, and business logic like isInputVisible and isRadioVisible in a separate Controller (bloc, etc) class. You can also subscribe to valueChanges in the email and input fields like:

// some initialization (within a constructor or some init method)
email.valueChanges.listen(_onEmailValueChanges);
input.valueChanges.listen(_onInputValueChanges);

.....

// implementation of the value change handlers
void _onEmailValueChanges(String? value) {
    if (isInputVisible) {
      input.markAsEnabled();
    } else {
      input.reset(disabled: true);
    }
  }

void _onInputValueChanges(String? value) {
  if (isRadioVisible) {
     radio.markAsEnabled();
  } else {
    radio.reset(disabled: true);
  }
}

Remember to cancel subscriptions at disposing time.

With the previous implementation, the View (Widgets) definitions would be even simpler.

joanpablo avatar Aug 21 '23 20:08 joanpablo