Zum Inhalt springen
aviral gupta

// B5.1 · ca. 30 Min. · Einstieg

Mit State auf Eingaben reagieren

Nach dieser Lektion bauen Sie ein Formular deklarativ: Sie beschreiben jeden visuellen Zustand, halten einen Status-String im State und lassen Handler zwischen den Zuständen wechseln.

Lektion 1 von 6 in B5 State verwalten

Anfang des Moduls

Danach können Sie

  • Eine UI deklarativ beschreiben: ihre visuellen Zustände auflisten und die menschlichen und maschinellen Eingaben, die die Wechsel auslösen
  • Den visuellen Zustand mit einem Status-String darstellen und State entfernen, der nicht wesentlich ist
  • Handler anschließen, die den Status setzen, das Formular beim Absenden deaktivieren und einen Fehler zeigen
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus Lektion B3.5: Objekte kopieren. Der Handler kopiert form zweimal und ändert jedes Mal ein Feld. Sie klicken einmal auf Send. Was zeigt das <p> (ohne Leerzeichen an den Enden)?

    function Survey() {
      const [form, setForm] = useState({ answer: 'Lima', status: 'typing' });
    
      function handleClick() {
        setForm({ ...form, status: 'sent' });
        setForm({ ...form, answer: '' });
      }
    
      return (
        <>
          <p>{form.answer} ({form.status})</p>
          <button onClick={handleClick}>Send</button>
        </>
      );
    }
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie voraus, bevor Sie weiterlesen. Quiz hält ein Boolean pro visuellem Zustand. Sie klicken einmal auf Submit. Was zeigen die <p>-Elemente?

    function Quiz() {
      const [isTyping, setIsTyping] = useState(true);
      const [isSubmitting, setIsSubmitting] = useState(false);
    
      return (
        <>
          {isTyping && <p>Typing</p>}
          {isSubmitting && <p>Submitting</p>}
          <button onClick={() => setIsSubmitting(true)}>Submit</button>
        </>
      );
    }
  3. Üben · Aufgabe 3 von 7

    Ergänzen Sie den Status, sodass die Textarea deaktiviert ist, während die Antwort geprüft wird, und danach wieder aktiv.

    <textarea
      value={answer}
      onChange={e => setAnswer(e.target.value)}
      disabled={status === '____'}
    />
    disabled={status === ''}
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jeder Eingabe, menschlich oder maschinell, den visuellen Zustand zu, in den das Quizformular wechseln soll.

  5. Üben · Aufgabe 5 von 7

    Dieser catch-Block vergisst eine Zeile. Sie tippen Rome, klicken auf Submit und warten auf die Prüfung. Was zeigen das <p> und die Textarea? (<p> / Textarea)

    // Pretend to ask a server: 50 ms, then a verdict.
    function check(answer) {
      return new Promise((resolve, reject) => {
        setTimeout(() => answer === 'Lima' ? resolve() : reject(new Error('Wrong answer')), 50);
      });
    }
    
    function Quiz() {
      const [answer, setAnswer] = useState('');
      const [error, setError] = useState(null);
      const [status, setStatus] = useState('typing');
    
      if (status === 'success') {
        return <h1>Right!</h1>;
      }
    
      async function handleSubmit(e) {
        e.preventDefault();
        setStatus('submitting');
        try {
          await check(answer);
          setStatus('success');
        } catch (err) {
          setError(err);
        }
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <textarea
            value={answer}
            onChange={e => setAnswer(e.target.value)}
            disabled={status === 'submitting'}
          />
          <button disabled={answer.length === 0 || status === 'submitting'}>
            Submit
          </button>
          {error !== null && <p>{error.message}</p>}
        </form>
      );
    }
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. isEmpty liegt neben answer im State. Sie tippen L und löschen es wieder. Ist der Button nach jedem Schritt deaktiviert? (nach L / nach dem Löschen)

    function Quiz() {
      const [answer, setAnswer] = useState('');
      const [isEmpty, setIsEmpty] = useState(true);
    
      function handleChange(e) {
        setAnswer(e.target.value);
        setIsEmpty(answer.length === 0);
      }
    
      return (
        <>
          <textarea value={answer} onChange={handleChange} />
          <button disabled={isEmpty}>Submit</button>
        </>
      );
    }
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe: ein Newsletter-Formular. Schreiben Sie in main.jsx eine Komponente Newsletter mit einem E-Mail-Eingabefeld und einem Button Subscribe. Simulieren Sie einen Server mit einer Funktion subscribe(email), die 50 ms wartet und dann erfüllt wird, wenn die Adresse ein @ enthält, und sonst mit einem Fehler abgelehnt wird. Halten Sie email, error und einen Status-String im State. Deaktivieren Sie Eingabefeld und Button während des Absendens und den Button, solange das Feld leer ist. Zeigen Sie nach einem Fehlschlag die Meldung des Fehlers und nach einem Erfolg Thanks for subscribing!. Führen Sie es mit ada aus, dann mit ada@example.com.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Das Städtequiz, deklarativ verdrahtet

Das ist das Quizformular von react.dev mit einer Prüfung von 50 ms statt 1,5 s. Sein State ist answer, error und status. Das JSX beschreibt jeden visuellen Zustand: Die Textarea ist beim Absenden deaktiviert, der Button auch bei leerer Antwort, der Fehler erscheint, wenn es einen gibt, und bei Erfolg wird statt des Formulars eine Überschrift zurückgegeben. Ein Lauf hängt die Komponente nur ein, also tippen Timer unten in main.jsx Istanbul, senden ab, berichten zweimal, tippen dann Lima und senden ab. Im Browser bleibt die Seite aktiv: Probieren Sie es selbst.

main.jsx

import { useState } from 'react';

export default function Form() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing');

  if (status === 'success') {
    return <h1>That's right!</h1>;
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await submitForm(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  function handleTextareaChange(e) {
    setAnswer(e.target.value);
  }

  return (
    <>
      <h2>City quiz</h2>
      <p>
        In which city is there a billboard that turns air into drinkable water?
      </p>
      <form onSubmit={handleSubmit}>
        <textarea
          value={answer}
          onChange={handleTextareaChange}
          disabled={status === 'submitting'}
        />
        <br />
        <button disabled={answer.length === 0 || status === 'submitting'}>
          Submit
        </button>
        {error !== null && <p className="Error">{error.message}</p>}
      </form>
    </>
  );
}

function submitForm(answer) {
  // Pretend it's hitting the network: 50 ms, then a verdict.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let shouldError = answer.toLowerCase() !== 'lima';
      if (shouldError) {
        reject(new Error('Good guess but a wrong answer. Try again!'));
      } else {
        resolve();
      }
    }, 50);
  });
}

// A run only mounts. Act like a user: answer Istanbul, then Lima.
function typeAnswer(text) {
  const textarea = document.querySelector('textarea');
  const setValue = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(textarea), 'value').set;
  setValue.call(textarea, text);
  textarea.dispatchEvent(new window.Event('input', { bubbles: true }));
}

function report(moment) {
  const textarea = document.querySelector('textarea');
  const error = document.querySelector('.Error');
  console.log(moment + ': textarea ' + (textarea.disabled ? 'disabled' : 'enabled') + ', ' + (error ? error.textContent : 'no error'));
}

setTimeout(() => {
  typeAnswer('Istanbul');
  setTimeout(() => {
    document.querySelector('button').click();
    setTimeout(() => {
      report('Submitting');
      setTimeout(() => {
        report('After the check');
        typeAnswer('Lima');
        setTimeout(() => document.querySelector('button').click(), 20);
      }, 100);
    }, 10);
  }, 20);
}, 20);

Ausführen mit

npm run dev

Ausgabe

Submitting: textarea disabled, no error
After the check: textarea enabled, Good guess but a wrong answer. Try again!
<!-- #root -->
<div id="root"><h1>That's right!</h1></div>
  • Kein Handler deaktiviert oder aktiviert etwas: Ein Aufruf von setStatus ändert den Status, und das JSX entscheidet, was das bedeutet.
  • Nach einer falschen Antwort geht der Status zurück auf typing; man kann die Antwort also korrigieren, während der Fehler sichtbar bleibt.
  • Erfolg ist ein frühes return: Die Überschrift ersetzt das Formular.
  • Das Absenden ist eine menschliche Eingabe, behandelt von onSubmit; die Antwort der Prüfung ist eine maschinelle Eingabe, die nach await ankommt.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Übungen

Übung 1 von 2

Ein Hauptstadtquiz mit Status

CapitalQuiz fragt nach der Hauptstadt Australiens, und checkAnswer(answer) simuliert einen Server: Nach 50 ms wird es für Canberra erfüllt und sonst mit einem Fehler abgelehnt. Verdrahten Sie es deklarativ. Ergänzen Sie den State error und status, 'typing', 'submitting' oder 'success'. Submit ist deaktiviert, solange die Antwort leer ist; Textarea und Submit sind beim Absenden deaktiviert. Eine falsche Antwort zeigt die Meldung des Fehlers und aktiviert das Formular wieder; die richtige ersetzt das Formular durch eine Überschrift Correct!.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Hinweise
  1. Hinweis 1

    Beginnen Sie mit const [status, setStatus] = useState('typing'); und const [error, setError] = useState(null);.

  2. Hinweis 2

    In handleSubmit: setStatus('submitting'), dann try { await checkAnswer(answer); setStatus('success'); } catch (err) { setStatus('typing'); setError(err); }.

  3. Hinweis 3

    Vor dem return: if (status === 'success') return <h1>Correct!</h1>;. Die Meldung zeigen Sie mit {error !== null && <p>{error.message}</p>}.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import { useState } from 'react';

export function checkAnswer(answer) {
  // Pretend it's asking a server: 50 ms, then a verdict.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (answer.trim().toLowerCase() === 'canberra') {
        resolve();
      } else {
        reject(new Error('Not quite. Try again!'));
      }
    }, 50);
  });
}

export default function CapitalQuiz() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing'); // 'typing', 'submitting' or 'success'

  if (status === 'success') {
    return <h1>Correct!</h1>;
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await checkAnswer(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        What is the capital of Australia?
        <textarea
          value={answer}
          onChange={e => setAnswer(e.target.value)}
          disabled={status === 'submitting'}
        />
      </label>
      <button disabled={answer.length === 0 || status === 'submitting'}>
        Submit
      </button>
      {error !== null && <p className="error">{error.message}</p>}
    </form>
  );
}
Auf dem eigenen Computer ausführen

Installieren Sie React 19.3 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.jsx

import { useState } from 'react';

export function checkAnswer(answer) {
  // Pretend it's asking a server: 50 ms, then a verdict.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (answer.trim().toLowerCase() === 'canberra') {
        resolve();
      } else {
        reject(new Error('Not quite. Try again!'));
      }
    }, 50);
  });
}

export default function CapitalQuiz() {
  const [answer, setAnswer] = useState('');
  // TODO: add error and status state, and use them below.

  async function handleSubmit(e) {
    e.preventDefault();
    // TODO: submitting, then success, or typing with an error.
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        What is the capital of Australia?
        <textarea value={answer} onChange={e => setAnswer(e.target.value)} />
      </label>
      <button>Submit</button>
    </form>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen, fireEvent} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import CapitalQuiz from './main.jsx';

const submit = () => screen.getByRole('button', {name: 'Submit'});

test('Submit ist deaktiviert, bis etwas getippt wurde', async () => {
  const user = userEvent.setup();
  render(<CapitalQuiz />);
  assert.equal(submit().disabled, true, 'bei leerer Antwort ist Submit deaktiviert: ' + submit().disabled);
  await user.type(screen.getByRole('textbox'), 'Sydney');
  assert.equal(submit().disabled, false, 'nach dem Tippen ist Submit deaktiviert: ' + submit().disabled);
});

test('Während der Prüfung ist das Formular deaktiviert; eine falsche Antwort zeigt den Fehler', async () => {
  const user = userEvent.setup();
  render(<CapitalQuiz />);
  await user.type(screen.getByRole('textbox'), 'Sydney');
  // fireEvent clicks synchronously, so the 50 ms check cannot have finished yet.
  fireEvent.click(submit());
  assert.equal(screen.getByRole('textbox').disabled, true, 'direkt nach Submit ist die Textarea deaktiviert: ' + screen.getByRole('textbox').disabled);
  assert.equal(submit().disabled, true, 'direkt nach Submit ist der Button deaktiviert: ' + submit().disabled);
  const message = await screen.findByText('Not quite. Try again!');
  assert.ok(message, 'nach einer falschen Antwort erscheint die Fehlermeldung');
  assert.equal(screen.getByRole('textbox').disabled, false, 'nach einer falschen Antwort ist die Textarea deaktiviert: ' + screen.getByRole('textbox').disabled);
});

test('Eine richtige Antwort ersetzt das Formular durch Correct!', async () => {
  const user = userEvent.setup();
  render(<CapitalQuiz />);
  await user.type(screen.getByRole('textbox'), 'Canberra');
  await user.click(submit());
  const heading = await screen.findByRole('heading', {name: 'Correct!'});
  assert.ok(heading, 'nach der richtigen Antwort erscheint eine Überschrift Correct!');
  assert.equal(screen.queryAllByRole('textbox').length, 0, 'nach der richtigen Antwort verbleibende Textfelder: ' + screen.queryAllByRole('textbox').length);
});

Legen Sie main.jsx in einem Vite-React-Projekt (npm create vite@latest, Vorlage react) als App.jsx in src/ ab und starten Sie es mit npm run dev. React rendert es dort wie hier in <StrictMode>.

Programm ausführen:

npm run dev

Für die Prüfungen gibt es noch keinen Befehl auf Ihrem Computer. Sie stehen in main.test.jsx, geschrieben mit node:test und React Testing Library.

Übung 2 von 2

Ein Profileditor mit zwei Zuständen

EditProfile zeigt immer seine beiden Felder, und sein Button tut nichts. Geben Sie ihm zwei visuelle Zustände. Ansehen: Jeder Name steht als fetter Text in <b>, und der Button heißt Edit Profile. Bearbeiten: Jeder Name ist ein Eingabefeld, und der Button heißt Save Profile. Ein Absenden wechselt zwischen beiden. Ändern Sie keine Elemente von Hand; halten Sie ein Boolean isEditing im State und lassen Sie das JSX beide Zustände beschreiben. Die Begrüßung darunter zeigt immer die aktuellen Namen.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Hinweise
  1. Hinweis 1

    Ergänzen Sie const [isEditing, setIsEditing] = useState(false); und kehren Sie es in handleSubmit um: setIsEditing(!isEditing).

  2. Hinweis 2

    Für jeden Namen: {isEditing ? <input … /> : <b>{firstName}</b>}.

  3. Hinweis 3

    Der Text des Buttons: {isEditing ? 'Save' : 'Edit'} Profile.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import { useState } from 'react';

export default function EditProfile() {
  const [firstName, setFirstName] = useState('Jane');
  const [lastName, setLastName] = useState('Jacobs');
  const [isEditing, setIsEditing] = useState(false);

  function handleSubmit(e) {
    e.preventDefault();
    setIsEditing(!isEditing);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        First name:{' '}
        {isEditing ? (
          <input value={firstName} onChange={e => setFirstName(e.target.value)} />
        ) : (
          <b>{firstName}</b>
        )}
      </label>
      <label>
        Last name:{' '}
        {isEditing ? (
          <input value={lastName} onChange={e => setLastName(e.target.value)} />
        ) : (
          <b>{lastName}</b>
        )}
      </label>
      <button type="submit">{isEditing ? 'Save' : 'Edit'} Profile</button>
      <p><i>Hello, {firstName} {lastName}!</i></p>
    </form>
  );
}
Auf dem eigenen Computer ausführen

Installieren Sie React 19.3 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.jsx

import { useState } from 'react';

export default function EditProfile() {
  const [firstName, setFirstName] = useState('Jane');
  const [lastName, setLastName] = useState('Jacobs');
  // TODO: the fields are always shown, and the button does nothing.

  function handleSubmit(e) {
    e.preventDefault();
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        First name:{' '}
        <input value={firstName} onChange={e => setFirstName(e.target.value)} />
      </label>
      <label>
        Last name:{' '}
        <input value={lastName} onChange={e => setLastName(e.target.value)} />
      </label>
      <button type="submit">Edit Profile</button>
      <p><i>Hello, {firstName} {lastName}!</i></p>
    </form>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import EditProfile from './main.jsx';

const boxes = () => screen.queryAllByRole('textbox');
const greeting = () => screen.getByText(/^Hello,/).textContent;

test('Das Profil beginnt als Text, mit einem Button Edit Profile', () => {
  render(<EditProfile />);
  assert.equal(boxes().length, 0, 'vor dem Bearbeiten angezeigte Textfelder: ' + boxes().length);
  assert.ok(screen.getByRole('button', {name: 'Edit Profile'}), 'es gibt einen Button Edit Profile');
  assert.equal(screen.getByText('Jane').tagName, 'B', 'Jane steht in einem <b>, nicht in einem ' + screen.getByText('Jane').tagName);
});

test('Edit Profile zeigt beide Felder und einen Button Save Profile', async () => {
  const user = userEvent.setup();
  render(<EditProfile />);
  await user.click(screen.getByRole('button', {name: 'Edit Profile'}));
  assert.deepEqual(boxes().map((box) => box.value), ['Jane', 'Jacobs'], 'nach Edit Profile enthalten die Felder ' + JSON.stringify(boxes().map((box) => box.value)));
  assert.ok(screen.getByRole('button', {name: 'Save Profile'}), 'nach Edit Profile gibt es einen Button Save Profile');
});

test('Save Profile zeigt den bearbeiteten Namen als Text', async () => {
  const user = userEvent.setup();
  render(<EditProfile />);
  await user.click(screen.getByRole('button', {name: 'Edit Profile'}));
  await user.clear(boxes()[0]);
  await user.type(boxes()[0], 'Ada');
  await user.click(screen.getByRole('button', {name: 'Save Profile'}));
  assert.equal(boxes().length, 0, 'nach Save Profile angezeigte Textfelder: ' + boxes().length);
  assert.equal(screen.getByText('Ada').tagName, 'B', 'Ada steht in einem <b>');
  assert.equal(greeting(), 'Hello, Ada Jacobs!', 'nach dem Speichern lautet die Begrüßung ' + JSON.stringify(greeting()));
});

Legen Sie main.jsx in einem Vite-React-Projekt (npm create vite@latest, Vorlage react) als App.jsx in src/ ab und starten Sie es mit npm run dev. React rendert es dort wie hier in <StrictMode>.

Programm ausführen:

npm run dev

Für die Prüfungen gibt es noch keinen Befehl auf Ihrem Computer. Sie stehen in main.test.jsx, geschrieben mit node:test und React Testing Library.

Häufige Fehler

Ein value ohne onChange-Handler

import { useState } from 'react';

export default function Form() {
  const [answer, setAnswer] = useState('');

  return (
    <form>
      <textarea value={answer} />
      <button disabled={answer.length === 0}>Submit</button>
    </form>
  );
}

Was React ausgibt

You provided a `value` prop to a form field without an `onChange` handler.

Warum, und die Lösung

Mit value={answer} zeigt React genau den State, also ändert Tippen nichts, solange kein Handler answer aktualisiert. React warnt, dass das Feld schreibgeschützt ist. Schließen Sie den Handler an, Schritt 5: onChange={e => setAnswer(e.target.value)}.

Den Fehler lesen, bevor es einen gibt

import { useState } from 'react';

export default function Form() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);

  return (
    <form>
      <textarea value={answer} onChange={e => setAnswer(e.target.value)} />
      <button disabled={answer.length === 0}>Submit</button>
      <p className="Error">{error.message}</p>
    </form>
  );
}

Was React ausgibt

TypeError: Cannot read properties of null (reading 'message')

Warum, und die Lösung

error beginnt mit null, und schon das erste Rendern liest error.message, also wird ein Fehler geworfen (Wortlaut von Chrome und Node; Firefox und Safari formulieren ihn anders). Zeigen Sie die Meldung nur, wenn es einen Fehler gibt: {error !== null && <p className="Error">{error.message}</p>}.

Das Fehlerobjekt selbst rendern

import { useState } from 'react';

export default function Form() {
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing');

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error('Wrong answer')), 50);
      });
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <button disabled={status === 'submitting'}>Submit</button>
      {error !== null && <p className="Error">{error}</p>}
    </form>
  );
}

// A run only mounts. Act like a user: submit once.
setTimeout(() => document.querySelector('button').click(), 50);

Was React ausgibt

Objects are not valid as a React child (found: [object Error]).

Warum, und die Lösung

error enthält ein Error-Objekt, und React kann ein Objekt nicht als Kind rendern; das Rendern nach der fehlgeschlagenen Prüfung wirft also einen Fehler. Rendern Sie stattdessen seinen Text: <p className="Error">{error.message}</p>.

React im Browser: React 19.3.0 (Entwicklungs-Build) und React Testing Library, MIT, von dieser Website ausgeliefert; JSX übersetzt TypeScript 6.0.3, Apache-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Beschreiben, was zu sehen ist, nicht wie es sich ändert

Ohne React schreiben Sie Befehle für jedes Element: beim Absenden den Button deaktivieren, den Spinner zeigen; bei einem Fehler die Meldung zeigen, das Formular wieder aktivieren. react.dev vergleicht diesen imperativen Stil damit, einem Fahrer jede Abzweigung anzusagen. Für kleine Beispiele geht das, wird aber schnell schwierig, weil jedes neue Element bedeutet, alle alten Befehle zu prüfen. In React beschreiben Sie die UI für jeden visuellen Zustand, so wie Sie einem Taxifahrer nur das Ziel nennen. Sie schreiben nie button.disabled = true, sondern disabled={status === 'submitting'}, und React aktualisiert die Seite, wenn sich der State ändert.

Die fünf Schritte

Erstens: Listen Sie die visuellen Zustände auf, die jemand sehen kann, beim Quizformular leer, Tippen, Absenden, Erfolg und Fehler. Ein Mock, der über eine Prop status gesteuert wird, zeigt jeden davon, bevor es Logik gibt. Zweitens: Finden Sie, was die Wechsel auslöst: menschliche Eingaben wie Klicken oder Tippen und maschinelle Eingaben wie eine eintreffende Netzwerkantwort oder ein abgelaufener Timer. Drittens: Stellen Sie den State mit useState im Speicher dar. Viertens: Entfernen Sie State, der nicht wesentlich ist. Fünftens: Schließen Sie Event-Handler an, die den State setzen. Menschliche Eingaben brauchen meist Event-Handler; maschinelle kommen in Code an, auf den Sie warten.

Ein Status-String statt vieler Booleans

Ein erster Versuch hat oft ein Boolean pro visuellem Zustand: isEmpty, isTyping, isSubmitting, isSuccess, isError. Sie können sich widersprechen: isTyping und isSubmitting können beide true sein, und kein Bildschirm sollte das zeigen. Stellen Sie drei Fragen. Entsteht ein Widerspruch? Dann fassen Sie die Booleans zu einem status zusammen, der 'typing', 'submitting' oder 'success' ist. Steckt die Information schon in einer anderen Variable? isEmpty ist answer.length === 0. Ist sie das Gegenteil einer anderen? isError ist error !== null. Das Quiz behält nur answer, error und status, und jedes Stück State ist wesentlich: Ohne eines davon funktioniert das Formular nicht mehr.

Quellen

Zuletzt geprüft am 5. Oktober 2026