hook_form_alter() Ajax suffix replace I'm trying to rewrite a form item's suffix on an AJAX callback and it keeps prepending the first choice and then doing nothing else. My code:
function my_dashboard_form_alter(&amp;$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id){
  if($form_id == 'node_job_listing_owner_job_form_form'){
    $tid = $form_state-&gt;getValue('field_job_description')[0]['target_id'];
    if(empty($tid)){
      $tid = $form['field_job_description']['widget']['#default_value'][0];
    }
    $link = null;
    if(!empty($tid)){
      $link_url = \Drupal\Core\Url::fromRoute('entity.taxonomy_term.canonical', [
        'taxonomy_term' =&gt; $tid
      ], [
        'attributes' =&gt; [
          'class' =&gt; ['use-ajax'],
          'data-dialog-type' =&gt; 'dialog',
          'data-dialog-options' =&gt; \Drupal\Component\Serialization\Json::encode(['width' =&gt; 420]),
          'data-dialog-renderer' =&gt; 'off_canvas',
        ],
      ]);
      $link = \Drupal::l('Preview Job Description...'.$tid, $link_url);
    }

    $form['field_job_description']['#suffix'] = $link;
    $form['field_job_description']['widget']['#ajax'] = [
      'callback' =&gt; 'updateJobDescriptionPreviewLink',
      'wrapper' =&gt; 'edit-field-job-description-wrapper',
      'event' =&gt; 'change',
    ];
  }
}

function updateJobDescriptionPreviewLink(&amp;$form, \Drupal\Core\Form\FormStateInterface $form_state){
  return $form['field_job_description'];
}

Basically, I'm trying to put a link below the &quot;Job Description&quot; drop down to preview the description of that term. I have the term id appended to the link text temporarily so I can see what happens. When I edit an existing node, it renders the link correctly the first time (i.e. &quot;Preview Job Description...102&quot;). But then, if I select a different option, it prepends another link to the existing suffix (i.e. &quot;Preview Job Description...99 Preview Job Description...102&quot;). Both links work correctly, but obviously two links aren't what I want. And also after that, it won't change anymore no matter what I select.
I feel like there's something simple I'm overlooking, but I confess Drupal AJAX has always confused me somewhat. Any ideas? Thanks!
I think the estrange behaviour it's because the suffix is after and not inside the wrapper, thus not get replaced and since you're ajaxly returning $form['field_job_description']; which contains the wrapper; thus, you end up with two same ID wrappers and fails on the second run.
Move the link generation to a function.
Here is an alternative AJAX approach where we target the link wrapper rather than the form wrapper.
function my_dashboard_form_alter(&amp;$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id){
  if($form_id == 'node_job_listing_owner_job_form_form'){
    $link = _my_dashboard_get_term_link($form, $form_state);
    $form['field_job_description']['#suffix'] = '&lt;div class=&quot;term-link&quot;&gt;' . $link . '&lt;/div&gt;'; // wrap link
    $form['field_job_description']['widget']['#ajax'] = [ // removed 'wrapper' =&gt;
      'callback' =&gt; 'updateJobDescriptionPreviewLink',
      'event' =&gt; 'change',
    ];
  }
}

function updateJobDescriptionPreviewLink(&amp;$form, \Drupal\Core\Form\FormStateInterface $form_state){
  $link = _my_dashboard_get_term_link($form, $form_state);
  $response = new \Drupal\Core\Ajax\AjaxResponse();
  $response-&gt;addCommand(new \Drupal\Core\Ajax\HtmlCommand(&quot;.term-link&quot;, $link));
  return $response;
}

function _my_dashboard_get_term_link(&amp;$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  $tid = $form_state-&gt;getValue('field_job_description')[0]['target_id'];
  if(empty($tid)){
    $tid = $form['field_job_description']['widget']['#default_value'][0];
  }
  $link = null;
  if(!empty($tid)){
    $link_url = \Drupal\Core\Url::fromRoute('entity.taxonomy_term.canonical', [
      'taxonomy_term' =&gt; $tid
    ], [
      'attributes' =&gt; [
        'class' =&gt; ['use-ajax'],
        'data-dialog-type' =&gt; 'dialog',
        'data-dialog-options' =&gt; \Drupal\Component\Serialization\Json::encode(['width' =&gt; 420]),
        'data-dialog-renderer' =&gt; 'off_canvas',
      ],
    ]);
    $link = \Drupal::l('Preview Job Description...'.$tid, $link_url);
    return $link;
  }
}