The Art of State: Zustandsmanagement in React-Anwendungen, Teil 1

Seite 2: Der Klassiker: lokaler State mit useState-Hook

Inhaltsverzeichnis

Der "klassische" lokale State wird wie oben gesehen mit dem Hook useState erzeugt. Sobald React die Komponente, die diesen Zustand erzeugt hat, aus dem DOM entfernt (etwa durch das Wechseln auf eine andere Seite), verschwindet auch der Zustand. Er ist also an den Lebenszyklus der Komponente gekoppelt. Von der Komponente ausgehend lässt sich der Zustand aber per Property an eine unterliegende Komponente reichen. Ebenso kann man einer unterliegenden Komponente eine Callback-Funktion zum Ändern des Zustands übergeben. Schon an der Stelle kann man sich darüber streiten, ab wann es sich um lokalen oder bereits um globalen Zustand handelt.

Die React Hook API bietet die Möglichkeit, eigene, sogenannte Custom Hooks zu schreiben. Wenn in einer Anwendung häufig wiederkehrende Muster vorkommen, kann es hilfreich sein, dafür eigene Hooks zur Verfügung zu stellen. In Custom Hooks lassen sich weitere Hooks verwenden, also auch der Hook useState. Besonders daran ist: Wenn sich der Zustand innerhalb des Custom Hook ändert, wird die Komponente ebenfalls neu gerendet, die den Hook verwendet. Custom Hooks sind technisch gesehen normale JavaScript-Funktionen, bei denen sich Rückgabewert und Funktionssignatur (im Gegensatz zu Komponentenfunktionen) frei wählen lassen. Auf diese Weise können Entwickler eine passende, fachliche API wählen. Das folgende Listing zeigt einen einfachen "Hello World"- Custom Hook, der einen einfachen Zähler zur Verfügung stellt. Ähnlich wie die useState-API liefert er den aktuellen Wert des Zählers sowie eine Funktion zum Ändern des Zählers zurück, allerdings in Form eines Objekts und nicht als Array wie bei useState.

// Custom Hook
function useCounter(initial) {
     const [ value, setValue ] = React.useState(initial);
     return {
          count: value,
          increaseCounter() { setValue(value + 1) }
     }
}

// Exemplarische Verwendung des Hook in einer Komponente
function LikeButton() {
     const { count, increaseCounter } = useCounter(10);
     return (
       <button onClick={increaseCounter}>{count} Likes</button>
     );
}

Ein realistischeres Szenario als das "Hello World" ist ein Custom Hook zum Zugriff auf eine Remote API, beispielsweise mit der Browser-API fetch. Das folgende – vereinfachte – Beispiel zeigt einen Custom Hook, der via fetch Daten von einer API lädt. Die URL der API wird dem Custom Hook per Argument übergeben. Der Hook liefert einen Indikator (isLoading) zurück, der anzeigt, ob der Request gerade läuft. Nach dem Laden der Daten werden die Daten ebenfalls zurückgeliefert.

function useApi(url) {
     const [ loading, setLoading ] = React.useState(false);
     const [ data, setData ] = React.useState(null);
     React.useEffect( () => {
          setLoading(true);
          fetch(url)
          .then(res => res.json())
          .then(data => {
               // Server Request beendet
               setLoading(false);
               setData(data);
          })
     }, [url]);
     return { loading, data };
}

Mit den Informationen können Verwender des Hooks abhängig von dessen Zustand eine Information an die Benutzer ausgeben, dass die Daten gerade geladen werden, oder die übertragenen Daten anzeigen. In der Blog-Beispielanwendung lässt sich dieser Hook etwa verwenden, um die Übersichtsseite zu laden beziehungsweise einen Blog-Post für die Einzeldarstellung:

// Blog-Übersicht:
function BlogListPage() {
  const { loading, data } = useApi("http://api/posts");
     if (loading) {
          return <LoadingIndicator />
     }
     return <BlogList posts={data} />
}

// Einzeldarstellung:
function BlogPage(props) {
  const { loading, data } = 
    useApi("http://api/posts/" + props.postId);

  if (loading) {
    return <LoadingIndicator />
  }
  return <Blog post={data} />
}