Display Bug when using #states (Forms API) with Ajax Request

Created on 14 March 2011, almost 14 years ago
Updated 22 August 2024, 4 months ago

FAPI #states do not update properly after AJAX requests.

Steps to reproduce

To replicate the bug, you need a content type with a field that makes an AJAX request and another field that has a state dependent on that field. In my use case, I have a content type with an image field and a text field that should only show when an image has been uploaded.
1. Have a content type with an image and a text field
2. Create a custom module that adds a state to your text field to only display when the image field has value. It would look something like this:

use Drupal\Core\Form\FormStateInterface;

function mymodule_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if ($form_id == 'node_page_form' || $form_id == 'node_page_edit_form') {
    $form['field_my_text_field']['#states'] = [
      'visible' => [
        ':input[name="files[field_my_image_field_0]"]' => ['filled' => TRUE],
      ],
    ];
  }
}

3. Go to your content type and upload an image. You would expect to see field_my_text_field because the image field has an image and the state should react to the AJAX call. However, you won't see field_my_text_field. Save your node, and go back to edit your node. Now you should see field_my_text_field.
4. Apply the patch from #80 and go through the steps again. Now on step 3, you should see field_my_text_field immediately after you upload an image.

Original report

I use a custom module which makes heavy use of the Forms API to do mathematical calculations.

The form which you can see at http://linsenrechner.de/node/27 is rendered with the #states attribute. each line uses a container to display two fields. When these fields are filled, a new line should appear.
This works perfect, BEFORE the calculation button is pressed and the ajax request is done.
After this, the behaviour of the form changes: When two fields of a specific line are filled not only one new line appears. In this case, all lines, which are defined in the form, appear at the same time.

The author of the custom module, sukr_s (http://drupal.org/user/554988) wasn't able to exactly locate the bug, but believes it's in states.js

Here are the form arrays, which are used to create the form at http://linsenrechner.de/node/27

$form['container0'] = array(
   '#type'   => 'container',
   '#weight'        => -53,
); 

$form['container0']['markup0'] = array(
   '#weight'        => -52,
   '#markup' => "<div id='ocalc_header_33'></div>
                 <div id='ocalc_header_33'>$ocalc_sag_height</div>
                 <div id='ocalc_header_33'>$ocalc_zone</div>",
); 


$form['container0'][$wrapper]['sag0'] = array(
    '#type'          => 'textfield',
    '#title'         => $ocalc_sag_height." 0",
    '#title_display' => 'invisible',
    '#weight'        => -51,
    '#maxlength'     => 4,
    '#size'          => 4,
    '#prefix'  => "<div class='form-item-sag0'>$ocalc_zone 0</div>",
    '#field_suffix'  => "mm",
  );

$form['container0'][$wrapper]['zone0'] = array(
    '#type'          => 'textfield',
    '#title'         => $ocalc_zone." 0",
    '#title_display' => 'invisible',
    '#weight'        => -50,
    '#maxlength'     => 4,
    '#size'          => 4,
    '#field_suffix'  => "mm",
  );

$form['container1'] = array(
   '#type'   => 'container',
   '#weight'        => -43,
   '#states' => array('visible' => array(
		':input[name=sag0]'  => array ('filled' => TRUE),
		':input[name=zone0]' => array ('filled' => TRUE),
    ),
  ),
); 


$form['container1'][$wrapper]['sag1'] = array(
    '#type'          => 'textfield',
    '#title'         => $ocalc_sag_height." 1",
    '#title_display' => 'invisible',
    '#weight'        => -42,
    '#maxlength'     => 4,
    '#size'          => 4,
    '#field_suffix'  => "mm", 
    '#prefix' => "<div class='form-item-sag1'>$ocalc_zone 1</div>",
  );
  
 $form['container1'][$wrapper]['zone1'] = array(
    '#type'          => 'textfield',
    '#title'         => $ocalc_zone." 1",
    '#title_display' => 'invisible',
    '#weight'        => -41,
    '#maxlength'     => 4,
    '#size'          => 4,
    '#field_suffix'  => "mm",                   
  );
 
$form['container2'] = array(
   '#type'   => 'container',
   '#weight'        => -33,
   '#states' => array('visible' => array(
		':input[name=sag0]'  => array ('filled' => TRUE),
		':input[name=zone0]' => array ('filled' => TRUE),
		':input[name=sag1]'  => array ('filled' => TRUE),
		':input[name=zone1]' => array ('filled' => TRUE),
    ),
  ),
); 


$form['container2'][$wrapper]['sag2'] = array(
    '#type'          => 'textfield',
    '#title'         => $ocalc_sag_height." 2",
    '#title_display' => 'invisible',
    '#weight'        => -32,
    '#maxlength'     => 4,
    '#size'          => 4,
    '#field_suffix'  => "mm", 
    '#prefix' => "<div class='form-item-sag1'>$ocalc_zone 2</div>",
  );
  
 $form['container2'][$wrapper]['zone2'] = array(
    '#type'          => 'textfield',
    '#title'         => $ocalc_zone." 2",
    '#title_display' => 'invisible',
    '#weight'        => -31,
    '#maxlength'     => 4,
    '#size'          => 4,
    '#field_suffix'  => "mm",                 
  );

 
$form['container3'] = array(
   '#type'   => 'container',
   '#weight'        => -23,
   '#states' => array('visible' => array(
		':input[name=sag0]'  => array ('filled' => TRUE),
		':input[name=zone0]' => array ('filled' => TRUE),
		':input[name=sag1]'  => array ('filled' => TRUE),
		':input[name=zone1]' => array ('filled' => TRUE),
		':input[name=sag2]'  => array ('filled' => TRUE),
		':input[name=zone2]' => array ('filled' => TRUE),
    ),
  ),
); 


$form['container3'][$wrapper]['sag3'] = array(
    '#type'          => 'textfield',
    '#title'         => $ocalc_sag_height." 3",
    '#title_display' => 'invisible',
    '#weight'        => -22,
    '#maxlength'     => 4,
    '#size'          => 4,
    '#field_suffix'  => "mm", 
    '#prefix' => "<div class='form-item-sag1'>$ocalc_zone 3</div>",
  );
  
 $form['container3'][$wrapper]['zone3'] = array(
    '#type'          => 'textfield',
    '#title'         => $ocalc_zone." 3",
    '#title_display' => 'invisible',
    '#weight'        => -21,
    '#maxlength'     => 4,
    '#size'          => 4,
    '#field_suffix'  => "mm",                 
  );

$form['container4'] = array(
   '#type'   => 'container',
   '#weight'        => -13,
   '#states' => array('visible' => array(
		':input[name=sag0]'  => array ('filled' => TRUE),
		':input[name=zone0]' => array ('filled' => TRUE),
		':input[name=sag1]'  => array ('filled' => TRUE),
		':input[name=zone1]' => array ('filled' => TRUE),
		':input[name=sag2]'  => array ('filled' => TRUE),
		':input[name=zone2]' => array ('filled' => TRUE),
		':input[name=sag3]'  => array ('filled' => TRUE),
		':input[name=zone3]' => array ('filled' => TRUE),
    ),
  ),
); 


$form['container4'][$wrapper]['sag4'] = array(
    '#type'          => 'textfield',
    '#title'         => $ocalc_sag_height." 4",
    '#title_display' => 'invisible',
    '#weight'        => -12,
    '#maxlength'     => 4,
    '#size'          => 4,
    '#field_suffix'  => "mm", 
    '#prefix' => "<div class='form-item-sag1'>$ocalc_zone 4</div>",
  );
  
 $form['container4'][$wrapper]['zone4'] = array(
    '#type'          => 'textfield',
    '#title'         => $ocalc_zone." 4",
    '#title_display' => 'invisible',
    '#weight'        => -11,
    '#maxlength'     => 4,
    '#size'          => 4,
    '#field_suffix'  => "mm",                   
  );
๐Ÿ› Bug report
Status

Needs work

Version

11.0 ๐Ÿ”ฅ

Component
Javascriptย  โ†’

Last updated 1 day ago

  • Maintained by
  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom @justafish
  • ๐Ÿ‡ซ๐Ÿ‡ทFrance @nod_
Created by

๐Ÿ‡ฉ๐Ÿ‡ชGermany culfin

Live updates comments and jobs are added and updated live.
  • Needs backport to D7

    After being applied to the 8.x branch, it should be considered for backport to the 7.x branch. Note: This tag should generally remain even after the backport has been written, approved, and committed.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands seanB Netherlands

    Another attempt to reroll #155.

  • Status changed to Needs review almost 2 years ago
  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia gauravvvv Delhi, India

    Fixed CCF, attached interdiff for same. Please review

  • Status changed to Needs work almost 2 years ago
  • The Needs Review Queue Bot โ†’ tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

    Consult the Drupal Contributor Guide โ†’ to find step-by-step guides for working with issues.

  • ๐Ÿ‡ฆ๐Ÿ‡นAustria DrColossos

    #172 applies and works in 9.5.3

  • ๐Ÿ‡จ๐Ÿ‡ดColombia yovanny.gomez.oyola

    #172 applies and works in 9.5.5. Thanks!

  • last update over 1 year ago
    Patch Failed to Apply
  • last update over 1 year ago
    30,324 pass, 2 fail
  • ๐Ÿ‡ซ๐Ÿ‡ทFrance Nicolas S. Lyon, France

    Patch #172 works for me with a Drupal 9.5.9

  • Status changed to Needs review over 1 year ago
  • 45:32
    43:56
    Running
  • ๐Ÿ‡ซ๐Ÿ‡ทFrance Nicolas S. Lyon, France

    Fix patch for Drupal 10.1.x

  • Status changed to Needs work over 1 year ago
  • ๐Ÿ‡ฑ๐Ÿ‡ฐSri Lanka SajithAthukorala

    Having Issues with #132 ( โ†’ ) , It gives an errors on page load where we use #states attributes in fields. This may cause the page load js errors and not recommend to use it . Thank You !!!

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States adam-delaney

    I've tested Drupal 10.1 with a clean install using the steps in the description to reproduce the issue and I'm not able to reproduce it. This seems to have been fixed in states.js for Drupal 10.

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia maithili11

    Patch #172 works for me with a Drupal 9.5.8

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States dave reid Nebraska USA

    I can no longer replicate this issue on Drupal 9.5 or 10 and I think the fix in #1988968: Drupal.ajax does not guarantee that "add new JS file to page" commands have finished before calling said JS โ†’ core issue, but I haven't been able to confirm that. I think we need someone to provide a reproducible scenario (I used the one in the issue description, and I could no longer reproduce it) or possibly close this as fixed. I do wish I knew exactly what core issue seems to have resolved it, I don't feel easy closing this until we know.

  • last update about 1 year ago
    29,666 pass, 2 fail
  • I'm still having this issue on a form submit button whose state depends on some facets elements. #177 resolves the issue for me. I'll try to produce a trimmed down reproducible scenario like the one I'm experiencing soonโ„ข.

  • ๐Ÿ‡ง๐Ÿ‡ชBelgium Robin.Houtevelts

    I can also confirm I'm still having this problem on 10.1.
    Applying #177 resolved it for me.

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia keshavv India

    Confirm I'm still having this problem on 10.2.
    Applying #186 resolved it for me.

  • Open in Jenkins โ†’ Open on Drupal.org โ†’
    Environment: PHP 8.1 & MySQL 5.7 updated deps
    last update 12 months ago
    Build Successful
  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom jonathanshaw Stroud, UK

    @robin.houtevelts @keshavv steps to reproduce - or at least come comments on the circumstances in which you're encountering this bug - would be really helpful.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States chrisgross

    I'm still having what I think is the same issue and the latest patch does not fix it. Perhaps this is a slightly complicated example, but I will simplify it as best as possible.

    Here I have I two select lists. The first one uses an ajax callback to modify the options contained within the second one, which has a couple hardcoded default values:

    $form['billing_date'] = [
      '#type' => 'select',
      '#title' => t('Billing Month'),
      '#options' => $options,
      'callback' => '::equipmentUpdate',
    ];
    
    $form['equipment'] = [
      '#type' => 'container',
    ];
    
    $form['equipment']['equipment_id'] = [
      '#type' => 'select',
      '#title' => t('Equipment Number'),
      '#options' => [
        0 => '- Select - ',
        'other' => 'Enter manually'
      ],
    ];
    

    My custom equipmentUpdate callback does a lot of other things, but here is the command I use to update the options of the second select list:

    $response->addCommand(new ReplaceCommand('#edit-equipment .form-item-equipment-id', $equipment)
    

    This ReplaceCommand occurs after $equipment (which is a copy of $form['equipment']['equipment_id']) has been modified to add new values after the two defaults set earlier.

    That functionality works just fine, but what does not is the state of a third field, whose visibility depends on a value contained within the second select list, which is modified by the ajax call:

    $form['equipment']['equipment_id_other'] = [
      '#type' => 'textfield',
      '#title' => t('Equipment Number'),
      '#states' => [
        'visible' => [
          ':input[name="equipment_id"]' => [
            'value' => 'other'
          ],
        ],
      ],
    ];
    

    Before the ajax callback, the visibility of this text field is correctly controlled by the equipment_id select list. After the callback completes, this no longer works, and the visibility of this field does not change. I have tested by experimenting with other ways of organizing the data and using different ajax commands, like HtmlCommand, but nothing works. No matter what I do, subsequent ajax calls continue to correctly modify the select list values, but the visibility state of the text field is broken.

    I have confirmed that all the markup in all fields and wrappers are exactly the same before and after the ajax command, except that that ajax operation changes element ids, for example, from edit-equipment-id to edit-equipment-id--Ronn-Os05Q0, but my visibility selector is targeting the name attribute, which does not change, so this shouldn't matter.

    I have been able to work around this by not using ReplaceCommand to replace the entire select element and instead manipulate the option elements directly:

    foreach ($results as $result) {
      $id = $result['id'];
      $response->addCommand(new RemoveCommand('#edit-equipment select option[value="other"] ~ option'));
      $response->addCommand(new AfterCommand('#edit-equipment select option[value="other"]', '<option value="' . $id . '">' . $id . '</option>'));
    }
    

    This works, but should not be necessary, as replacing the entire select list should not cause the states of another field to stop working. Doing it this way is also mildly inconvenient because, as far as I can tell, I cannot pass options as a render array (or a subset thereof) to the ajax commands, so I have to write the markup for each option manually.

    I get no console errors or anythig

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia gaurav_manerkar Vasco Da Gama, Goa

    Doesn't work correctly when the form is nested, e.g inline_entity_form module

  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands e.escribano

    It doesn't work for forms in modals that layout paragraphs use. I have that problem. The funny thing is that it was working before but after a Drupal core update or a module update the states I had stopped working.

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia sukr_s

    @eescribanoc check if your issue is related to ๐Ÿ› JS #states behavior does not have a detach method Needs review

Production build 0.71.5 2024