Formulare

HTML-Formularelemente funktionieren in React ein bisschen anders als DOM-Elemente, weil Formularelemente einen natürlichen internen State besitzen. Beispielsweise akzeptiert dieses HTML-Formular einen einzelnen Namen:

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Absenden" />
</form>

Dieses Formular hat das Standard HTML Verhalten, dass es beim Absenden den Benutzer zu einer neuen Seite weiterleitet. Wenn du dieses Verhalten in React willst, funktioniert es einfach. In den meisten Fällen ist es jedoch sinnvoll, eine JavaScript-Funktion zu benutzen, die das Absenden des Formulars übernimmt und Zugriff auf die Eingabedaten des Benutzers hat. Um dies zu erreichen, wird standardmäßig eine Technik namens “kontrollierte Komponenten” verwendet.

Kontrollierte Komponenten

Formelemente wie <input>, <textarea> und <select> behalten in HTML typischerweise ihren eigenen Zustand bei und aktualisiert ihn basierend auf Benutzereingaben. In React wird der veränderbare State typischerweise in der State-Eigenschaft der Komponente gehalten und nur mit setState() aktualisiert.

Wir können beides kombinieren, indem wir den React-State zur alleinigen “source of truth” machen. Die React-Komponente rendert dann das Formular und steuert ebenso die nachfolgenden Benutzereingaben. Ein Eingabeformular-Element, dessen Wert auf diese Art und Weise von React kontrolliert wird, wird “kontrollierte Komponente” genannt.

Wenn wir zum Beispiel das vorherige Formular-Beispiel nehmen und den eingegbenen Namen, wenn er abgeschickt wird, loggen, dann können wir das Formular als kontrollierte Komponente schreiben:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Ein Name wurde abgeschickt: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Absenden" />
      </form>
    );
  }
}

Auf CodePen ausprobieren

Da das value-Attribut unseres Formular-Elementes gesetzt wurde, entspricht der angezeigte Wert immer this.state.value und macht somit den React-State zur “source of truth”. Da handleChange bei jedem Tastendruck ausgeführt wird, um den React-State zu aktualisieren, wird der Wert es ebenso, sobald Benutzereingaben stattfinden.

Bei einer kontrollierten Komponente hat jede Änderung des States eine dazugehörige Handler-Funktion. Dadurch ist es einfacher Benutzereingaben zu ändern oder zu validieren. Wenn wir zum Beispiel immer wollen, dass die Namen in Großbuchstaben geschrieben werden, können wir handleChange so schreiben:

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()});
}

Der textarea-Tag

In HTML definiert ein <textarea>-Element seinen Text durch seine Kinder:

<textarea>
  Hallo, dies ist ein bisschen Text im textarea-Tag
</textarea>

In React benutzt eine <textarea> stattdessen das value-Attribut. Auf diese Weise kann ein Formular, das eine <textarea> benutzt, sehr ähnlich geschrieben werden wie ein Formular, das ein einziges Eingabefeld verwendet:

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Bitte schreibe einen Aufsatz über dein Lieblings-DOM-Element.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Ein Aufsatz wurde eingereicht: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Aufsatz:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Absenden" />
      </form>
    );
  }
}

Merke dir, dass this.state.value im Konstruktor initialisert wird, so dass das Textfeld mit etwas Text gefüllt wird.

Der select-Tag

In HTML erstellt <select> eine Dropdown-Liste. Zum Beispiel erstellt dieses HTML eine Dropdown-Liste von Geschmacksrichtungen:

<select>
  <option value="grapefruit">Pampelmuse</option>
  <option value="lime">Limette</option>
  <option selected value="coconut">Kokosnuss</option>
  <option value="mango">Mango</option>
</select>

Beachte, dass die Option “Kokosnuss” aufgrund des selected-Attributs zunächst ausgewählt ist. React benutzt statt dem selected-Attribut, ein value-Attribut auf dem select-Tag. Dies ist in einer kontrollierten Komponente komfortabler, da du sie nur an einer Stelle bearbeiten musst. Zum Beispiel:

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Dein Lieblingsgeschmack ist: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite flavor:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Pampelmuse</option>
            <option value="lime">Limette</option>
            <option value="coconut">Kokosnuss</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Absenden" />
      </form>
    );
  }
}

Auf CodePen ausprobieren

Zusammenfassend ist es so, dass <input type="text">, <textarea> und <select> sehr ähnlich funktionieren - alle besitzen ein value-Attribut, welches benutzt wird, um eine kontrollierte Komponente umzusetzen.

Hinweis

Du kannst ein Array an das value-Attribut übergeben um somit mehrere Optionen im select-Tag auszuwählen:

<select multiple={true} value={['B', 'C']}>

Der file input-Tag

In HTML, an <input type="file"> lets the user choose one or more files from their device storage to be uploaded to a server or manipulated by JavaScript via the File API.

<input type="file" />

Da dessen Wert schreibgeschützt ist, ist dies eine unkontrollierte Komponente in React. Es wird später zusammen mit anderen unkontrollierten Komponenten in der Dokumentation behandelt.

Umgang mit mehreren Eingabefeldern

Wenn du mehrere kontrollierte input-Elemente benötigst, kannst du das name-Attribut zu jedem Element hinzufügen und die Handler-Funktion entscheiden lassen, was, basierend auf dem Wert von event.target.name, zu tun ist.

Zum Beispiel:

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Wird hingehen:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Anzahl der Gäste:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

Auf CodePen ausprobieren

Beachte, dass wir die ES6-Syntax für berechnete Bezeichnernamen verwenden, um den State ensprechend dem gegebenem Namen zu ändern:

this.setState({
  [name]: value
});

Ist das Äquivalent zu diesem ES5-Code:

var partialState = {};
partialState[name] = value;
this.setState(partialState);

Da setState() automatisch die einzelne Teile in den aktuellen State überführt, benötigen wir nur den Aufruf der geänderten Teile:

Gesteuerte Null-Wert-Eingaben

Das Angeben eines value-Prop auf einer [kontrollierten Komponente(/docs/forms.html#controlled-components) verhindert, dass der Benutzer den Eingabewert ändern kann, es sei denn, du wünscht es. Wenn du einen Wert angegeben hast, aber das Eingabefeld noch editierbar ist, hast du möglicherweise versehentlich value auf undefiniert oder null gesetzt.

Der folgende Code demonstriert dies. (Das Eingabefeld ist zuerst gesperrt, wird aber nach einer kurzen Verzögerung editierbar.)

ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

Alternativen zu kontrollierten Komponenten

Es kann manchmal mühsam sein kontrollierte Komponenten zu verwenden, da du für jede Art und Weise der Änderung deiner Daten einen Event-Handler bereitstellen und den gesamten Eingabe-State durch die React-Komponente leiten musst. Dies kann besonders ärgerlich sein, wenn du eine bestehende Codebasis zu React konvertieren möchtest oder eine Nicht-React-Bibliothek in einer React-Anwendung integrierst. In diesen Situationen solltest du vielleicht unkontrollierte Komponenten ausprobieren, eine alternative Technik zur Implementierung von Eingabeformularen.

Vollumfassende Lösungen

Wenn du nach einer Komplettlösung mit Validierung, dem Im-Blick-Behalten von besuchten Feldern und der Handhabung von Formularverarbeitung suchst, ist Formik eine der beliebtesten Entscheidungen. Es basiert jedoch auf den selben Pinzipien von kontrollierten Komponenten und der Verwaltung des States - also vernachlässige es nicht, darüber etwas zu lernen.