Zum Inhalt springen

State mit Objekten und Arrays

Wenn der State ein Objekt oder ein Array ist, reicht es nicht, einfach eine Setter-Funktion aufzurufen. Du musst verstehen, wie React Änderungen erkennt — und warum Immutability dabei die entscheidende Rolle spielt.

React erkennt eine State-Änderung nicht dadurch, dass du Werte veränderst — sondern dadurch, dass du neue Werte übergibst. React vergleicht beim Setter-Aufruf intern: „Ist das der gleiche Wert wie vorher?” Bei primitiven Werten (string, number, boolean) ist das einfach. Bei Objekten und Arrays vergleicht React die Referenz — also ob es dasselbe Objekt im Speicher ist.

Das bedeutet: Wenn du ein Objekt im State direkt mutierst und dann denselben Verweis übergibst, sieht React keinen Unterschied:

// ❌ Falsch — React bemerkt die Änderung nicht
const [person, setPerson] = useState({ name: "Dan", alter: 30 });
function handleClick() {
person.alter = 31; // das Objekt wird mutiert...
setPerson(person); // ...aber person ist noch die gleiche Referenz
}

Statt das bestehende Objekt zu verändern, erstellst du immer ein neues Objekt und übergibst es:

// ✅ Richtig — neues Objekt, neue Referenz, React rendert neu
function handleClick() {
setPerson({ name: "Dan", alter: 31 });
}

Behandle State-Objekte so, als wären sie eingefroren (read-only). Du liest sie, aber du veränderst sie nie — du ersetzt sie.


Wenn du nur ein Feld eines Objekts ändern willst, kopierst du das bestehende Objekt mit dem Spread-Operator und überschreibst nur das eine Feld:

const [person, setPerson] = useState({
name: "Dan",
email: "dan@example.com",
alter: 30,
});
function handleNameChange(event) {
setPerson({
...person, // alle bestehenden Felder kopieren
name: event.target.value, // dieses Feld überschreiben
});
}

...person kopiert alle Felder des Objekts in das neue Objekt. Felder, die danach angegeben werden, überschreiben das Kopierte.

Wenn du ein Formular mit mehreren Feldern verwaltest, kannst du einen einzigen Handler für alle Felder schreiben — mit dem berechneten Eigenschaftsnamen [event.target.name]:

function AnmeldeFormular() {
const [formular, setFormular] = useState({
name: "",
email: "",
nachricht: "",
});
function handleChange(event) {
setFormular({
...formular,
[event.target.name]: event.target.value,
});
}
return (
<form>
<input name="name" value={formular.name} onChange={handleChange} />
<input name="email" value={formular.email} onChange={handleChange} />
<textarea name="nachricht" value={formular.nachricht} onChange={handleChange} />
</form>
);
}

[event.target.name] ist ein berechneter Eigenschaftsname: Der Wert des name-Attributs des Inputs wird als Schlüssel im Objekt verwendet.

Spread ist flach — er kopiert nur die oberste Ebene. Bei verschachtelten Objekten musst du auf jeder Ebene spreaden:

const [person, setPerson] = useState({
name: "Dan",
adresse: {
stadt: "Berlin",
land: "Deutschland",
},
});
function handleStadtChange(event) {
setPerson({
...person,
adresse: {
...person.adresse, // die innere Ebene kopieren
stadt: event.target.value, // dann überschreiben
},
});
}

Bei tiefer Verschachtelung wird das schnell unübersichtlich. In diesem Fall lohnt sich ein Blick auf Immer — eine Bibliothek, die Immutability hinter den Kulissen übernimmt und mutierenden Schreibstil erlaubt.


Arrays im State folgen demselben Prinzip: Du veränderst das bestehende Array nie — du erstellst immer ein neues.

Viele der gewohnten Array-Methoden mutieren das Original (push, pop, splice, sort, …). Für State-Updates verwendest du stattdessen Methoden, die ein neues Array zurückgeben.

Operation❌ Vermeiden (mutiert)✅ Verwenden (neues Array)
Element hinten anfügenpush(el)[...arr, el]
Element vorne anfügenunshift(el)[el, ...arr]
Element an Position einfügensplice(i, 0, el)[...arr.slice(0, i), el, ...arr.slice(i)]
Element entfernen (nach Wert)splice(i, 1)arr.filter(el => el !== ziel)
Element entfernen (nach Index)splice(i, 1)arr.filter((_, i) => i !== index)
Element aktualisierenarr[i] = neuerWertarr.map((el, i) => i === index ? neuerWert : el)
Objekt in Array aktualisierendirektes Feld setzenarr.map(el => el.id === id ? { ...el, feld: wert } : el)
Array umkehrenreverse()[...arr].reverse()
Array sortierensort()[...arr].sort()
Teilbereich kopierensplice()arr.slice(start, ende)
const [aufgaben, setAufgaben] = useState(["Einkaufen", "Kochen"]);
// Hinten anfügen
function hinzufuegen(neueAufgabe) {
setAufgaben([...aufgaben, neueAufgabe]);
}
// Vorne anfügen
function vornAnfuegen(neueAufgabe) {
setAufgaben([neueAufgabe, ...aufgaben]);
}
const [aufgaben, setAufgaben] = useState(["Einkaufen", "Kochen", "Sport"]);
// Nach Wert entfernen
function entfernen(aufgabe) {
setAufgaben(aufgaben.filter(a => a !== aufgabe));
}
// Nach Index entfernen
function entfernenNachIndex(index) {
setAufgaben(aufgaben.filter((_, i) => i !== index));
}
const [zaehler, setZaehler] = useState([0, 0, 0]);
function erhoehen(index) {
setZaehler(zaehler.map((wert, i) => i === index ? wert + 1 : wert));
}

Beim Kopieren eines Arrays mit Spread oder map werden nur die Referenzen auf die enthaltenen Objekte kopiert — die Objekte selbst bleiben dieselben. Du musst also auch das betroffene Objekt neu erstellen:

const [produkte, setProdukte] = useState([
{ id: 1, name: "Apfel", preis: 0.5 },
{ id: 2, name: "Brot", preis: 1.8 },
{ id: 3, name: "Milch", preis: 1.2 },
]);
function preisAktualisieren(id, neuerPreis) {
setProdukte(produkte.map(produkt =>
produkt.id === id
? { ...produkt, preis: neuerPreis } // neues Objekt mit geändertem Feld
: produkt // unverändertes Objekt
));
}
function einfuegenNach(index, neuesElement) {
setAufgaben([
...aufgaben.slice(0, index + 1),
neuesElement,
...aufgaben.slice(index + 1),
]);
}

sort() und reverse() mutieren das Original. Erstelle zuerst eine Kopie:

function sortieren() {
const sortiert = [...aufgaben].sort();
setAufgaben(sortiert);
}
function umkehren() {
setAufgaben([...aufgaben].reverse());
}

KonzeptBeschreibung
ImmutabilityState nie direkt ändern — immer neue Objekte/Arrays erstellen
Objekt aktualisieren{ ...obj, geaendertesfeld: neuerWert }
Verschachtelte ObjekteAuf jeder Ebene spreaden: { ...obj, nested: { ...obj.nested, feld: wert } }
Element anfügen[...arr, neuesElement] statt push
Element entfernenarr.filter(...) statt splice
Element aktualisierenarr.map(...) statt direkter Zuweisung
Sortieren/UmkehrenErst kopieren [...arr], dann sort() / reverse()