I'm attempting to allow the user to dynamically choose a number of fields based on a dropdown box using an ajax call, but I can't seem to get the ajax call to rebuild the form afterwards.

class AJAXexample extends BlockBase {
    public function blockForm($form, FormStateInterface $form_state) {
        if (empty($form_state->getValue('number'))) {
            $form_state->setValue('number', 3);
        }
        $form['columnNum'] = [
            '#title'   => t('Number of Columns'),
            '#type'    => 'select',
            '#options' => [
                1         => '1',
                2         => '2',
                3         => '3',
                4         => '4',
            ],
            '#default_value' => $this->configuration['columnNum'],
            '#empty_option'  => t('-select-'),
            '#ajax'          => [
                'callback'      => [$this, 'columnCallback'],
            ],
        ];
        for ($i = 0; $i < $form_state->getValue('number'); $i += 1) {
            $form['column'][$i] = [
                $i => [
                    '#type'       => 'details',
                    '#title'      => t('Column '.$numTitle),
                    '#open'       => FALSE,
                    'columnTitle' => [
                        '#type'      => 'textfield',
                        '#title'     => t('Column Title'),
                        '#value'     => $config[0]['columnTitle'],
                    ],  
                ],
            ];  
        return $form;
    }
 
    public function columnCallback(array&$form, FormStateInterface $form_state) {
        $form_state->setValue('number', 10);
        $form_state->setRebuild(true);
        return $form;
    }
}

The number of textfields is based on the form_state variable 'number'. The callback columnCallback changes the form_state variable to 10, and is fired when the 'columnNum' form field is changed. However the form isn't rebuilt with the new number of fields even though $form_state->setRebuild(); is called. Is there a way to get the form to rebuild after an ajax call?

Comments

goc222’s picture

Hello! Any progress on this? I am experiencing the same issue. When I try to return the field i get "AjaxRenderer::renderResponse() must be of the type array, null given". Upon inspecting of the $form['something'] I do get its null. It seems like an issue with forms inside of blocks only. I have been debugging for the past few hours with no luck so any pointers are appreciated.

Thank you!

kllrthr’s picture

I've got the same problem, can't seem to figure this out.

mcalabrese’s picture

You cannot alter $form_state from within an ajax callback.
In order to rebuild the form you have to add a submit handler.
Within the submit handler you can make changes to $form_state, which will be reflected in the ajax callback.

PrzemyslawKot’s picture

That's the most comprehensive and helpful answer ever written.

John Pitcairn’s picture

Brief, accurate and helpful. I'm ajaxifying a form provided by another module, and this is the key to having any modifications made in hook_form_alter() show up when modified form elements are replaced by ajax. You need to call $form_state->rebuildForm() in a submit callback, not in the ajax callback.

jlongbottom’s picture

I've added a submit handler but $form['fieldset'] is undefined.
 

public function blockForm($form, FormStateInterface $form_state) {
// some other fields go here

$form['fieldset']['actions']['add_item'] = [
      '#type' => 'submit',
      '#value' => t('Add item'),
      '#submit' => ['::addItem'],
      '#ajax' => [
        'callback' => [$this, 'addremoveItem'],
        'wrapper' => 'item-fieldset-wrapper',
      ],
    ];

$form_state->setCached(FALSE);
return $form;
}

public function addremoveItem(array &$form, FormStateInterface $form_state) {
    return $form['fieldset'];
  }

public function addItem(array &$form, FormStateInterface $form_state) {
    $num_items = $form_state->get('num_items') + 1;
    $form_state->set('num_items', $num_items);
    $form_state->setRebuild();
  }

building elegant solutions efficiently

PrzemyslawKot’s picture

For Drupal 8.

Put form_state->setRebuild in submitForm or validateForm. Both of those functions are run before ajax callback function (if we talk about submitting whole form, you have to check with other callbacks).

This is whole code to rebuild form(clear values) after ajax submit:

$userInput = $formState->getUserInput();
        $keys = $formState->getCleanValueKeys();

        $newInputArray = [];
        foreach ($keys as $key) {
            if ($key == "op")  continue;
            $newInputArray[$key] = $userInput[$key];
        }

        $formState->setUserInput($newInputArray);
        $formState->setRebuild(true);

You have to remove user input for those fields, but leave all data with form* keys.
I remove op. Don't know yet what it is, but it works for me without it.