Einführung
React Context ist ein mächtiges Werkzeug zur Verwaltung von Zuständen und Daten, die über die Komponentenhierarchie hinweg geteilt werden sollen, ohne sie explizit über Props weiterzureichen. Es ermöglicht eine zentrale Datenquelle, die von beliebigen Komponenten im Baum direkt abgerufen und aktualisiert werden kann. Dadurch vereinfacht Context vor allem die Handhabung globaler Zustände wie Benutzerinformationen, Theme-Einstellungen oder Spracheinstellungen, ohne auf komplexe State-Management-Bibliotheken zurückgreifen zu müssen.
Inhaltsverzeichnis
Problem - Prop-Weitergabe
In React fließen die Daten standardmäßig von oben nach unten (Eltern zu Kindern) durch Props. Diese unidirektionale Datenübertragung ist normalerweise gut verständlich und nachvollziehbar. Aber was passiert, wenn wir Daten an tief verschachtelte Komponenten weitergeben müssen?
Wir werden zuerst ein Beispiel erstellen, in dem das sogenannte Prop-Drilling eindeutig zu sehen ist. Sprich, in diesem Beispiel reichen wir von einem Component zum nächsten eine bestimmte Zustands-Eigenschaft durch. Dabei benötigen einige Components diese Eigenschaft bzw. diese Information gar nicht und reichen sie nur weiter.
In folgenden Beispiel wird gezeigt, wie man bestimmte Daten durch mehrere Components an das Ziel-Component übergeben kann.
const UserProfile = ({ username }) => {
return <span>Hallo, {username}</span>
};
const Navigation = ({ username }) => {
return (
<nav>
<UserProfile username={username} />
</nav>
);
};
const Header = ({ username }) => {
return (
<div>
<Navigation username={username} />
</div>
);
};
const UserComponent = () => {
const username = 'John';
return <Header username={username} />
};
export default UserComponent;
Klar, technisch funktioniert es und wir erhalten die Daten korrekt an allen Stellen, an die wir diese Daten übergeben. Allerdings ist es ziemlich umständlich. Die Annahme von Props durch bestimmte Components, die die Informationen gar nicht benötigen ist auch nicht sinnvoll.
Lösung - Context API
Mit der Context API kann man einen “Container” für die Daten erstellen, auf den jede Komponente im Baum zugreifen kann, ohne dass Zwischen-Komponenten involviert sein müssen.
Dieser Container wird mithilfe von Provider aufgebaut. Dieser muss an der obersten Ebene positioniert werden. Gemeint ist die Stelle, ab welcher die innliegenden Components auf die Daten zugreifen müssen.
import { createContext, useContext } from 'react';
// Container für Daten erzeugen
const UserContext = createContext();
const UserProfile = () => {
const username = useContext(UserContext);
return <span>Hallo, {username}</span>
};
const Navigation = () => {
return (
<nav>
<UserProfile />
</nav>
);
};
const Header = () => {
return (
<div>
<Navigation />
</div>
);
};
const UserComponent = () => {
const username = 'John';
return (
<UserContext.Provider value={username}>
<Header />
</UserContext.Provider>
);
};
export default UserComponent;
In diesem Beispiel ist Header
unser Top-Component. Das heißt, dass alle anderen Components Kind-Components sind. In diesem Fall müssen wir um dieses Component unseren Provider
als eine Art Wrapper platzieren (sodass die anderen Components Kinder von diesem Provider bzw. Wrapper werden).
Irgendwo in unserer Datei erzeugen wir diesen Container für Daten.
const UserContext = createContext();
Eigentlich könnte man dies auch in eine andere Datei auslagern, per export
bereitstellen und an benötigten Stellen wieder importieren. Das ist eine reine Organisationsangelegenheit.
Im UserProfile
Component verwenden wir useContext(UserContext)
, um diesen “Container” mit Daten zu verwenden. Wir lesen den aktuellen Stand aus und speichern diesen in der Variable username
;
const UserProfile = () => {
const username = useContext(UserContext);
return <span>Hallo, {username}</span>
};
Das UserComponent
ist unsere oberste Ebene für alle anderen Components aus diesem Beispiel. Hier definieren wir unseren Datenbestand. In diesem Beispiel ist sehr überschaubar.
const username = 'John';
Bevor wir unser Entry-Component (Header
) verwenden, müssen wir diese mit dem UserContext
Component umgeben. Wir stellen sogenannten Provider bereit. Als Wert value
übergeben wir unseren Variable username
mit dem Wert John
.
return (
<UserContext.Provider value={username}>
<Header />
</UserContext.Provider>
);
Die Formel für die Anwendung von Context über einen Provider lautet wie folgt.
<MyContext.Provider value={/* irgendein Wert */}>
{/* Kind-Komponenten, die auf den Context zugreifen können */}
</MyContext.Provider>
Beispiel - Objekt als Wert
Die Funkton createContext()
wird verwendet, um das Context-Objekt zu erstellen. Dabei kann die Funktion einen Parameter, den Startwert annehmen.
In diesem Beispiel werden wir einige Dinge anders machen.
- Wir lagern die Erstellung eines Contextes in einen eigenen Ordner und eine eigene Datei aus.
- Wir werden nicht mit einem primitiven Wert, sondern mit einem Objekt als Wert des Context-Containers arbeiten.
- Wir verwenden State Management, um bestimmte Werte im Context-Container zu aktualisieren.
1 - Context-Datei
Wir starten mit der Erstellung der Context-Datei. Dort erzeugen wir das Context-Objekt und setzen die initiale Daten. Wir arbeiten in diesem Beispiel mit einem Benutzer-Objekt.
import { createContext } from 'react';
const initUserData = () => {
return {
name: 'John Doe',
email: 'john@outlook.com',
age: 20,
job: 'Developer'
};
};
const UserContext = createContext(initUserData());
export default UserContext;
Damit haben wir unsere Context-Datei definiert.
Wenn wir einen Log unmittelbar nach dem Aufruf der createContext
Funktion platzieren, werden wir unsere initiale Daten in der Konsolenausgabe sehen.
// ...
const UserContext = createContext(initUserData());
console.log(UserContext);
export default UserContext;
Ab hier werden wir schrittweise das Beispiel ausbauen, um möglichst stufenweise das Verständnis zu steigern.
2 - Grundstruktur definieren
Zunächst definieren wir die Grundstruktur. Unser Ziel ist es aufzuzeigen, dass Prop Drilling bei größerem Component-Baum mit Context API kein Problem mehr ist. Hierfür definieren wir ein paar Components. Die Components werden ineinander verschachtelt sein.
const ComponentTop = () => {
return (
<>
<ComponentMiddle />
</>
);
};
const ComponentMiddle = () => {
return (
<>
<ComponentBottom />
</>
);
};
const ComponentBottom = () => {
return (
<button>Update email</button>
);
};
const UseContextObject = () => {
return (
<ComponentTop />
)
};
export default UseContextObject;
Das Prinzip ist hier vermutlich gut erkennbar. Wir bauen mit diesen Components eine kleine Hierarchie auf. Unser zentrales Component UseContextObject
ist unser Einstiegspunkt, das Root-Component in diesem Context, sozusagen.
3 - State definieren
Da wir vorhaben in diesem Beispiel bestimmte Daten zu aktualisieren und zwangsläufig irgendwo diese zwischenzuspeichern, verwenden wir den useState
Hook. Der Zustand wird mit den initialen Daten gesetzt, die wir über UserContext
erhalten, welche wir in der extra Datei definiert haben.
Außerdem definieren wir eine Funktion updateUserData
zum Aktualisieren der Daten.
import { useState } from 'react';
import UserContext from '../../state/UserContext';
const ComponentTop = () => {
return (
<>
<ComponentMiddle />
</>
);
};
const ComponentMiddle = () => {
return (
<>
<ComponentBottom />
</>
);
};
const ComponentBottom = () => {
return (
<button>Update email</button>
);
};
const UseContextObject = () => {
const [userData, setUserData] = useState(UserContext._currentValue);
const updateUserData = (newData) => {
setUserData(current => newData);
};
return (
<ComponentTop />
)
};
export default UseContextObject;
Somit haben wir unser State definiert. Als initialen Wert haben wir den aktuellen Datenbestand unseres Context-Containers übergeben UserContext._currentValue
.
Zusätzlich haben wir eine Funktion definiert, welche wir aufrufen werden, wenn Daten am Objekt userData
aktualisiert werden sollen.
4 - Context Provider
Nun möchten wir im ComponentTop
Component unsere Benutzerdaten ausgeben. Aktuell weiß dieses Component nichts über Benutzer und hat keinen Zugriff auf irgendwelche Daten. Ohne Weiteres wäre es also nicht möglich.
Damit alle Kind-Komponenten Zugriff auf den Datenbestand im Context-Container erhalten, müssen sie mit dem Context-Provider umgeben werden.
Man kann diesen an beliebiger Stelle einsetzen. Wichtig zu beachten, dass nur Components innerhalb von diesem Provider Zugriff auf die Daten haben.
Also werden wir nun unser Entry-Component ComponentTop
in der Funktion des UseContextObject
Components in diesem Context-Provider setzen.
Außerdem müssen wir einen Wert übergeben. An dieser Stelle übergeben wir ein zusammengesetztes Objekt, bestehend aus Benutzerdaten und der Funktion zum Aktualisieren der Benutzerdaten.
{ userData, updateUserData }
Wir ergänzen nun unseren Haupt-Component und setzen unseren Einstiegs-Component in den Context-Provider.
import { useState } from 'react';
import UserContext from '../../state/UserContext';
const ComponentTop = () => {
return (
<>
<ComponentMiddle />
</>
);
};
const ComponentMiddle = () => {
return (
<>
<ComponentBottom />
</>
);
};
const ComponentBottom = () => {
return (
<button>Update email</button>
);
};
const UseContextObject = () => {
const [userData, setUserData] = useState(UserContext._currentValue);
const updateUserData = (newData) => {
setUserData(current => newData);
};
return (
<UserContext.Provider value={{ userData, updateUserData }}>
<ComponentTop />
</UserContext>
)
};
export default UseContextObject;
5 - Zugriff auf den Context
Da wir jetzt unsere Components (weil sie alle Kind-Components von ComponentTop
sind) mit dem Zugriff auf den Context ausgestattet haben, können wir wie geplant im ComponentTop
die Benutzerdaten anzapfen und ausgeben.
Dafür verwenden wir den useContext
Hook, welcher uns eine bequeme Möglichkeit bereitstellt, um auf die Daten im Context-Container zuzugreifen.
import { useState, useContext } from 'react';
import UserContext from '../../state/UserContext';
const ComponentTop = () => {
const { userData } = useContext(UserContext);
return (
<>
<div className="user_info">
<h3>User Info</h3>
<p>Name: {userData.name}</p>
<p>E-Mail: {userData.email}</p>
<p>Age: {userData.age}</p>
<p>Job: {userData.job}</p>
</div>
<ComponentMiddle />
</>
);
};
const ComponentMiddle = () => {
return (
<>
<ComponentBottom />
</>
);
};
const ComponentBottom = () => {
return (
<button>Update email</button>
);
};
const UseContextObject = () => {
const [userData, setUserData] = useState(UserContext._currentValue);
const updateUserData = (newData) => {
setUserData(current => newData);
};
return (
<UserContext.Provider value={{ userData, updateUserData }}>
<ComponentTop />
</UserContext>
)
};
export default UseContextObject;
Damit haben wir bereits unsere Ausgabe ermöglicht, ohne die Props zu verwenden.
6 - Aktualisierung der Daten
Im abschließenden Schritt müssen wir dafür sorgen, dass wir die Daten aktualisieren können. Wir möchten es im untersten Component im Komponenten-Baum tun, nämlich in ComponentBottom
.
Was benötigen wir in diesem Component? Wir haben dort bereits unseren Button. Für diesen müssen wir einen Handler definieren, der unseren Klick-Event verarbeitet. Diese Handler-Funktion verknüpfen wir mit dem onClick
Event.
Zusätzlich müssen wir die, im Context-Objekt, vorhandenen Daten auslesen. Die Daten auslesen können wir aus unserem UserContext
Context-Objekt mithilfe der Funktion useContext
, wie wir es bereits an der anderen Stelle getan haben.
import { useState, useContext } from 'react';
import UserContext from '../../state/UserContext';
const ComponentTop = () => {
const { userData } = useContext(UserContext);
return (
<>
<div className="user_info">
<h3>User Info</h3>
<p>Name: {userData.name}</p>
<p>E-Mail: {userData.email}</p>
<p>Age: {userData.age}</p>
<p>Job: {userData.job}</p>
</div>
<ComponentMiddle />
</>
);
};
const ComponentMiddle = () => {
return (
<>
<ComponentBottom />
</>
);
};
const ComponentBottom = () => {
const { userData, updateUserData } = useContext(UserContext);
const updateEmail = () => {
updateUserData({ ...userData, email: 'john@gmail.com' });
};
return (
<button onClick={updateEmail}>Update email</button>
);
};
const UseContextObject = () => {
const [userData, setUserData] = useState(UserContext._currentValue);
const updateUserData = (newData) => {
setUserData(current => newData);
};
return (
<UserContext.Provider value={{ userData, updateUserData }}>
<ComponentTop />
</UserContext>
)
};
export default UseContextObject;
Was passiert im ComponentBottom
genau? Wir lesen unser UserContext
Objekt aus. Wir wissen, dass dort zwei Elemente gehalten werden. userData
, ein Objekt mit den Benutzerdaten und updateUserData
, eine Referenz auf die Funktion, welche die Benutzerdaten aktualisiert.
Wir haben einen Klick-Handler updateEmail
registriert. Diese Funktion wird bei jedem Klick auf den Button aufgerufen. Diese Funktion ruft wiederrum die Funktion zum Aktualisieren der Benutzerdaten aus updateUserData
auf. Nachdem diese Funktion aufgerufen wurde, ruft sie die setUserData
Update-Funktion für unseren State auf.

