Allowing unlimited values for a field in programmatically defined form I am trying to insert a custom form into a page in my Drupal 8 site. This form has 2 fields which need to accept unlimited values (one textfield, one fieldset).

In my current implementation, I have declared this form as a class, defined in the src folder of a module, and my .module file adds this form to the $build array via hook_node_view_alter():

$build['myform'] = \Drupal::formBuilder()-&gt;getForm('Drupal\my_module_hook\Form\MyForm');

My class constructs the form using public function buildForm(array $form, FormStateInterface $form_state). I am not sure how to set up a field such that it can accept unlimited values.

I'm aware that tutorials and answers on this topic using Ajax exist such as this and this, but all such examples are using $form_state as an array, which breaks my code as $form_state is an object.

Is building this form as a class the wrong way to do it? If not, how exactly do I accomplish what I'm trying to do without using $form_state as an array? If so, how can I insert my form into the page otherwise? I'm not understanding how other answers and tutorials on the subject are accomplishing this since these functions are usually shown out of context.
I have set up a module demonstrating how to allow the user to add unlimited values:
https://bitbucket.org/scotteuser/drupal-8-demo-form-unlimited-values

Here is the Form itself:

&lt;?php

namespace Drupal\demo_form_unlimited_values\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Class UnlimitedValuesForm.
 *
 * @package Drupal\demo_form_unlimited_values\Form
 */
class UnlimitedValuesForm extends FormBase {


  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'unlimited_values_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

    // State that the form needs to allow for a hierarchy (ie, multiple
    // names with our names key).
    $form['#tree'] = TRUE;

    // Initial number of names.
    if (!$form_state-&gt;get('num_names')) {
      $form_state-&gt;set('num_names', 1);
    }

    // Container for our repeating fields.
    $form['names'] = [
      '#type' =&gt; 'container',
    ];

    // Add our names fields.
    for ($x = 0; $x &lt; $form_state-&gt;get('num_names'); $x++) {
      $form['names'][$x]['first_name'] = [
        '#type' =&gt; 'textfield',
        '#title' =&gt; $this-&gt;t('First name @num', ['@num' =&gt; ($x + 1)]),
      ];

      $form['names'][$x]['last_name'] = [
        '#type' =&gt; 'textfield',
        '#title' =&gt; $this-&gt;t('Last name @num', ['@num' =&gt; ($x + 1)]),
      ];
    }

    // Button to add more names.
    $form['addname'] = [
      '#type' =&gt; 'submit',
      '#value' =&gt; $this-&gt;t('Add another name'),
    ];

    // Submit button.
    $form['submit'] = [
      '#type' =&gt; 'submit',
      '#value' =&gt; $this-&gt;t('Submit'),
    ];

    return $form;
  }

  /**
    * {@inheritdoc}
    */
  public function validateForm(array &amp;$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &amp;$form, FormStateInterface $form_state) {
    $values = $form_state-&gt;getValues();

    // Decide what action to take based on which button the user clicked.
    switch ($values['op']) {
      case 'Add another name':
        $this-&gt;addNewFields($form, $form_state);
        break;

      default:
        $this-&gt;finalSubmit($form, $form_state);
    }
  }

  /**
   * Handle adding new.
   */
  private function addNewFields(array &amp;$form, FormStateInterface $form_state) {

    // Add 1 to the number of names.
    $num_names = $form_state-&gt;get('num_names');
    $form_state-&gt;set('num_names', ($num_names + 1));

    // Rebuild the form.
    $form_state-&gt;setRebuild();
  }

  /**
   * Handle submit.
   */
  private function finalSubmit(array &amp;$form, FormStateInterface $form_state) {
    drupal_set_message($this-&gt;t('Do what you want with the final data here'), 'status');
  }

}


The critical bits are: 


$form[#tree] tells Drupal that the form is hierarchical.
$form_state-&gt;get() and $form_state-&gt;set() are used around the counter num_names
Check the 'op' to see which button the user pressed. If they pressed to add another field, use $form_state-&gt;set() to increase the count and $form_state-&gt;setRebuild(); to tell Drupal that the form should be rebuilt.


Hope that helps you get it working!