Context Verwendung
Die Verwaltung von Zuständen in React-Anwendungen stellt Entwickler oft vor Herausforderungen, besonders wenn Daten über mehrere Komponenten hinweg geteilt werden müssen. Das klassische Prop-Drilling, bei dem Daten durch mehrere Komponenten-Ebenen hindurchgereicht werden, führt schnell zu unübersichtlichem und schwer wartbarem Code.
Die Context API von React bietet hier eine elegante Lösung. Mit ihr lassen sich Daten zentral verwalten und an beliebige Komponenten in der Anwendung weitergeben - ohne den Umweg über Props. Besonders bei Funktionalitäten wie Themes, Benutzereinstellungen oder Authentifizierungsdaten zeigt sich die Stärke dieser Methode.
Inhaltsverzeichnis
Grundstruktur
Zuerst erstellen wir die nötige Grundstruktur, damit es besser verständlich ist. Dieses Beispiel organisiere ich in einem Ordner.
Folgende Struktur möchte ich erzeugen.
ThemeSample/
: OrdnerThemeSampleStyles.jsx
: Hier definieren wir StileThemeSampleContext.jsx
: Hier wird der Context definiertThemeSample.jsx
: Das Haupt-ComponentSubOne.jsx
: Component einsSubTwo.jsx
: Component zweiSubThree.jsx
: Component dreiSubFour.jsx
: Component vier
Context
In diesem Beispiel machen wir es möglich, dass die einzelnen Components ein globales Theme umschalten (setzen) können. Für diesen Zweck verwenden wir createContext
und useContext
. Entsprechend müssen wir irgendwo das zugehörige Context-Objekt erzeugen. Das machen wir in der ThemeSampleContext.jsx
.
import { createContext } from 'react';
const ThemeSampleContext = createContext({
theme: 'light',
updateTheme: () => {}
});
export default ThemeSampleContext;
Wir geben gleich einen Startwert in Form eines Objektes mit, welches den aktuellen Wert (Namen) des Themes und eine Funktion, die wir später für die Aktualisierung des Themes verwenden werden.
Das ist unser Start-Objekt (Start-Wert).
createContext({
theme: 'light',
updateTheme: () => {}
});
Styles
Ich halte in diesem Beispiel die Stile begrenzt. Sie spielen nicht die wichtigste Rolle. Aber ich zeige hier eine Möglichkeit von mehreren, wie man eigene Components gestalten kann.
Für dieses Vorhaben verwende ich Styled Components Bibliothek, welche uns eine tolle Möglichkeit zum Gestalten unserer Components und der Elemente innerhalb von Components gibt.
Hier nochmals eine kurze Referenz, wie man das Packet installiert.
npm install --save styled-components
Meine Styled Components speichere ich in einem Ordner und verwende sie an den entsprechenden Stellen. Dadurch halte ich meine Components etwas aufgeräumter.
In diesem Beispiel möchte ich meinem Haupt-Component, dem Container, eine paar bestimmte Stile hinzufügen und den einzelnen Components auch, sodass sie identisch aussehen.
In meinem Ordner erstelle ich die Datei ThemeSampleStyles.jsx
. Dort werden die Styled Components erstellt.
import styled from 'styled-components';
const ThemeSampleDiv = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
`;
const SubComWrapper = styled.div`
border: 2px solid;
height: 100%;
padding: 20px;
box-sizing: border-box;
.component_content {
display: flex;
justify-content: center;
align-items: center;
}
p {
display: block;
width: 100%;
}
`;
export {
ThemeSampleDiv,
SubComWrapper
};
Ich exportiere in meinen JavaScript (JSX) Dateien immer am Ende. So kann ich an einer Stelle was aus dieser Datei exportiert wurde und was nicht. Sind die Exporte an verschiedenen Stellen in der Datei verteilt und diese Datei ist etwas größer, muss ich ggf. genauer schauen und suchen, was exportiert ist.
Components
Ok, bis jetzt haben die beiden Dateien für Context ThemeSampleContext.jsx
und für Stile ThemeSampleStyles.jsx
. Sie beide sind voneinander unabhängig und machen tatsächlich auch nicht viel.
An diesen Punkt möchte ich die einzelnen Components erstellen. Ohne größere Funktion, als Grundgerüst für unsere weitere Entwicklung. Das einzige, was ich gleich in diesem Schritt mache, ist die Verwendung von Styled Component für die Einzel-Components, als Wrapper.
// Styles
import { SubComWrapper } from './ThemeSampleStyles';
const SubOne = () => {
return (
<SubComWrapper className="sub_component">
<h3>Component 1</h3>
<div className="component_content">
<p>Current theme:</p>
<button>Set theme one</button>
</div>
</SubComWrapper>
);
};
export default SubOne;
// Styles
import { SubComWrapper } from './ThemeSampleStyles';
const SubTwo = () => {
return (
<SubComWrapper className="sub_component">
<h3>Component 2</h3>
<div className="component_content">
<p>Current theme:</p>
<button>Set theme two</button>
</div>
</SubComWrapper>
);
};
export default SubTwo;
// Styles
import { SubComWrapper } from './ThemeSampleStyles';
const SubThree = () => {
return (
<SubComWrapper className="sub_component">
<h3>Component 3</h3>
<div className="component_content">
<p>Current theme:</p>
<button>Set theme three</button>
</div>
</SubComWrapper>
);
};
export default SubThree;
// Styles
import { SubComWrapper } from './ThemeSampleStyles';
const SubFour = () => {
return (
<SubComWrapper className="sub_component">
<h3>Component 4</h3>
<div className="component_content">
<p>Current theme:</p>
<button>Set theme four</button>
</div>
</SubComWrapper>
);
};
export default SubFour;
Diese Components binde ich nun in der ThemeSample.jsx
Datei ein. Ebenfalls verwende ich auch in dieser Haupt-Datei mein Styled Component, um meine Stile anzuwenden.
// Styles
import { ThemeSampleDiv } from './ThemeSampleStyles';
// Components
import SubOne from './SubOne';
import SubTwo from './SubTwo';
import SubThree from './SubThree';
import SubFour from './SubFour';
const ThemeSample = () => {
return (
<>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<p style={{ backgroundColor: '#ddd', padding: '10px' }}>
Current theme:
</p>
<button>Reset theme</button>
</div>
<ThemeSampleDiv className="theme_sample">
<SubOne />
<SubTwo />
<SubThree />
<SubFour />
</ThemeSampleDiv>
</>
);
};
export default ThemeSample;
Damit steht unsere Grundstruktur und wir können mit dem Einbau des Context und des States starten.
Context Provider und State
Wie wir wissen, müssen wir an einer bestimmten Stelle (möglichst weit oben im Komponentenbaum) unseren Context-Provider definieren, welcher alle Components einschließt, die Zugriff auf die Daten im Context-Objekt benötigen. In diesem Beispiel setzen wir alle einzelnen Components hinein.
Außerdem definieren wir eine einfache Zustandsverwaltung, welche uns Hilft das aktuell aktive Theme zu überwachen und zu aktualisieren.
// React
import { useState, useContext } from 'react';
// Styles
import { ThemeSampleDiv } from './ThemeSampleStyles';
// Components
import SubOne from './SubOne';
import SubTwo from './SubTwo';
import SubThree from './SubThree';
import SubFour from './SubFour';
const ThemeSample = () => {
const { currentTheme } = useContext(ThemeSampleContext);
const [theme, setTheme] = useState(currentTheme || 'light');
const handleThemeUpdate = newTheme => {
setTheme(current => newTheme);
};
return (
<ThemeSampleContext.Provider value={{
theme: theme,
updateTheme: handleThemeUpdate
}}>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}>
<p style={{ backgroundColor: '#ddd', padding: '10px' }}>
Current theme: {currentTheme}
</p>
<button>Reset theme</button>
</div>
<ThemeSampleDiv className="theme_sample">
<SubOne />
<SubTwo />
<SubThree />
<SubFour />
</ThemeSampleDiv>
</ThemeSampleContext.Provider>
);
};
export default ThemeSample;
Wir haben also nun alle unsere Components innerhalb folgender Struktur angesiedelt.
<ThemeSampleContext.Provider value={{
theme: theme,
updateTheme: handleThemeUpdate
}}>
...
</ThemeSampleContext.Provider>
Die Eigenschaft updateTheme
in unserem Context-Objekt erhält eine Referenz auf die handleThemeUpdate
Funktion. Das bedeutet, dass bei jedem Aufruf von updateTheme
eigentlich die handleThemeUpdate
Funktion aufgerufen wird. Diese Funktion ruft wiederrum die Funktion unserer Zustandsverwaltung setTheme
auf und aktualisiert den Wert.
Context in Components
Im letzten Schritt müssen wir nur noch die Verwendung von Context-Objekt und die ein Button-Klick-Event in den einzelnen Components implementieren.
Ich mache es anhand dem Beispiel für ThemeSample/SubOne.jsx
. Analog sollten alle anderen Components ebenfalls erweitert werden.
Wir benötigen die useContext
Funktion, um an den Wert des Context-Objektes heranzukommen. Außerdem müssen das Context-Objekt an sich importieren, da wir der useContext
Funktion mitteilen müssen, aus welchem Objekt gelesen werden soll.
// React
import { useContext } from 'react';
// Context
import ThemeSampleContext from '/ThemeSampleContext';
// Styles
import { SubComWrapper } from './ThemeSampleStyles';
const SubOne = () => {
const { theme, updateTheme } = useContext(ThemeSampleContext);
const onThemeSwitch = () => {
updateTheme('theme-one');
};
return (
<SubComWrapper className="sub_component">
<h3>Component 1</h3>
<div className="component_content">
<p>Current theme: <strong>{theme}</strong></p>
<button onClick={onThemeSwitch}>Set theme one</button>
</div>
</SubComWrapper>
);
};
export default SubOne;
Wenn man alle übrigen Components nach dem gleichen Schema erweitert, erhält man ein Ergebnis, bei dem jedes Component das globale Theme auslesen und aktualisieren kann.