Der useState()-Hook ist ein zentrales Element in der Zustandsverwaltung von React-Funktionskomponenten. Dabei spielt der richtige Umgang mit verschiedenen Datentypen eine wichtige Rolle. In diesem Artikel wird gezeigt, welche Datentypen mit useState() verwendet werden können, wie React intern damit umgeht und welche Besonderheiten bei Referenztypen wie Objekten oder Arrays beachtet werden sollten.

Datentypen

Sehr oft handelt es sich bei Zustandswerten um Zeichenketten (String) und Zahlen (Number). Man ist allerdings nicht auf diese Datentypen beschränkt.

Man kann jeden validen JavaScript-Datentyp verwenden. Das bedeutet, dass man nicht nur Zeichenketten oder Zahlen, sondern auch komplexere Typen wie Arrays oder Objekte verwenden kann. React erlaubt hier die Verwendung auch komplexer Typen als Zustandswerte.

Es ist sogar zur Laufzeit problemlos möglich den Datentyp vollständig von z.B. String nach Number zu wechseln. Ob es eine gute Praxis ist, steht auf einem anderen Blatt geschrieben, technisch ist es in React allerdings möglich.

Beispiele

Zeichenkette (String)

TypeScript StateExample.jsx
import { useState } from 'react';

const StateExample = () => {
    const [enteredEmail, setEnteredEmail] = useState('');
};

export default StateExample;

Zahlen (Number)

TypeScript StateExample.jsx
import { useState } from 'react';

const StateExample = () => {
    const [counter, setCounter] = useState(0);
};

export default StateExample;

Boolean

TypeScript StateExample.jsx
import { useState } from 'react';

const BooleanExample = () => {
    const [isActive, setIsActive] = useState(false);
};

export default BooleanExample;

Listen (Arrays)

TypeScript StateExample.jsx
import { useState } from 'react';

const ArrayExample = () => {
    const [enteredItems, setEnteredItems] = useState([]);

    const addItemHandler = () => {
        const time = new Date().getTime();
        setEnteredItems(prevItems => [...prevItems, time]);
    };

    return (
        <>
            {enteredItems.length > 0 && (
                <ul>
                    {enteredItems.map((todo, idx) => (
                        <li key={idx}>{todo}</li>
                    ))}
                </ul>
            )}
            <button onClick={addItemHandler}>
                Add item
            </button>
        </>
    );
};

export default ArrayExample;

Objekte (Object)

TypeScript ObjectExample.jsx
import { useState } from 'react';

const ObjectExample = () => {
    const [person, setPerson] = useState({
        name: 'John',
        age: 20,
        job: 'Designer',
        salary: 30000,
    });

    const updatePerson = () => {
        // IMMER neues Objekt — Spread + Override
        setPerson(prev => ({
            ...prev,
            job: 'Developer',
            salary: 50000,
        }));
    };

    return (
        <>
            <div>
                <p>Name: {person.name}</p>
                <p>Age: {person.age}</p>
                <p>Job: {person.job}</p>
                <p>Salary: {person.salary}</p>
            </div>
            <button onClick={updatePerson}>Update data</button>
        </>
    );
};

export default ObjectExample;

Map und Set — Sonderfall

Map und Set sind iterable Collections, aber für React State problematisch: ihre Mutations-Methoden (set, add, delete, clear) ändern die Instanz, nicht erzeugen eine neue. React sieht keinen Update.

TypeScript MapSet.jsx
const [tags, setTags] = useState(new Set());

// FALLE: setTags(tags.add('neu')) — gleiche Referenz, kein Re-Render
const addBuggy = (tag) => setTags(tags.add(tag));

// KORREKT: neue Set-Instanz
const addCorrect = (tag) => setTags(prev => new Set([...prev, tag]));

// Gleiches Prinzip bei Map
const [items, setItems] = useState(new Map());
const setItem = (k, v) => setItems(prev => new Map(prev).set(k, v));

Wer viel mit Map/Set arbeitet, kann auch eine Wrapper-Library wie immer nutzen oder das useReducer-Pattern bevorzugen.

null und undefined

Beide sind als State erlaubt — typisch bei „noch nicht geladenen" Werten.

TypeScript NullableState.jsx
const [user, setUser] = useState(null);

useEffect(() => {
    fetch('/api/me').then(r => r.json()).then(setUser);
}, []);

if (user === null) return <p>Lade…</p>;
return <p>Hi, {user.name}</p>;

TypeScript verlangt dann oft eine Typ-Annotation: useState<User | null>(null). Sonst inferiert TS null und Property-Zugriff geht nicht.

Was NICHT in State gehört

  • Klassen-Instanzen mit eigenem Lebenszyklus (z.B. WebSocket-Connection) — die gehören in useRef, weil Mutation an ihnen erwartet wird.
  • Funktionen (mit Vorbehalt) — wenn du sie wirklich speichern willst, in einen Function-Wrapper: setFn(() => myFn). Sonst interpretiert useState die Funktion als Lazy-Initialisierer.
  • Abgeleitete WerteisValid aus email lässt sich im Render-Body neu berechnen. Kein eigener State nötig.
  • DOM-Knoten — die kommen über useRef, nicht State.

Häufige Stolperfallen

Object-State NIE mutieren — Spread + neuer Wert.

state.foo = 5 ändert die Referenz nicht. React rendert nicht neu. Korrekt: setState(prev => (&#123;...prev, foo: 5&#125;)). Details im Immutability-Artikel.

Array-State: nicht push/pop/splice, sondern map/filter/concat.

Mutierende Array-Methoden ändern die Referenz nicht. Stattdessen: setArr(prev => [...prev, neu]), setArr(prev => prev.filter(x => x.id !== id)), setArr(prev => prev.map(x => x.id === id ? &#123;...x, ...patch&#125; : x)).

useState(funktion) ruft die Funktion als Lazy-Initializer auf.

useState(myFn) würde myFn() aufrufen und den Rückgabewert als Initialwert nehmen. Wer eine Funktion SPEICHERN will: in einen Wrapper packen — useState(() => myFn).

Map und Set sind tricky — neue Instanz pro Update nötig.

set.add(x) mutiert. Lösung: new Set([...prev, x]) oder new Map(prev).set(k, v). Bei häufigem Update kann useReducer klarer sein.

null als Initial-Wert braucht in TypeScript explizite Type-Annotation.

useState(null) inferiert TS als useState<null> — danach kann man nur null zuweisen. Lösung: useState<User | null>(null).

Class-Instanzen mit Identity (WebSocket, MutationObserver): useRef statt useState.

Solche Objekte haben Mutations-Lifecycle, der nicht zum „neue Referenz pro Update"-Modell passt. useRef hält die Instanz stabil, ohne Re-Renders bei „Änderungen".

Abgeleitete Werte gehören nicht in State.

const fullName = $&#123;firstName&#125; $&#123;lastName&#125; im Render-Body — kein eigener State. State-Duplikation führt zu Sync-Bugs.

Datentyp-Wechsel ist möglich, aber unsauber.

setState('text') nach Initial useState(0) ist technisch erlaubt. In TypeScript-Code wird das vom Compiler gefangen. In JS-Code: vermeiden, Datentyp pro State konsistent halten.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu State

Zur Übersicht