Provider Wrapper
Ein React Context Custom Provider Wrapper ist eine speziell angepasste Komponente, die den standardmäßigen Context Provider erweitert, um zusätzliche Logik, State-Management oder Funktionalitäten einzubinden. Statt den Context Provider direkt zu verwenden, kapselt dieser Wrapper häufig Initialisierungen, komplexe Zustandsverwaltung oder Nebenwirkungen und macht den Kontext dadurch flexibler und modularer. Solche Custom Provider Wrapper erleichtern die Wiederverwendbarkeit und zentrale Steuerung von gemeinsam genutzten Daten, indem sie den Kontext um spezifische Features oder Business-Logik erweitern, die über das reine Bereitstellen von Daten hinausgehen.
Inhaltsverzeichnis
Beschreibung
In diesem Beispiel möchten wir eine kleine App (oder einen Bereich) mit Übersetzungen aufbauen. Dabei verwenden wir einen eigenen Provider Wrapper, welcher den Zustand und ein paar Funktionen bereitstellt.
Ich werde zunächst einzelne Teile in Abhängigskeitsreihenfolge aufbauen und am Ende füge ich alles zusammen.
Übersetzungen
Im ersten Schritt erstellen wir die Datei und definieren unser Übersetzungsobjekt. Ebenfalls importieren wir unsere benötigte Funktionen. Die Übersetzungen sind in Form eines Objektes aufgebaut und werden anhand dem Schlüssel (Länder-Code) identifiziert.
// React
import {
createContext,
useContext,
useState
} from 'react';
// Translation object
const translations = {
de: {
welcome: 'Willkommen',
changeLanguage: 'Sprache ändern',
currentLanguage: 'Aktuelle Sprache',
greeting: 'Hallo React',
description: 'Das ist ein Beispiel für die Verwendung von Context.'
},
en: {
welcome: 'Welcome',
changeLanguage: 'Change language',
currentLanguage: 'Current language',
greeting: 'Hello React',
description: 'This is an example of usage of context.'
},
fr: {
welcome: 'Bienvenue',
changeLanguage: 'Changer de langue',
currentLanguage: 'Langue actuelle',
greeting: 'Bonjour React',
description: 'Ceci est un exemple d\'utilisation du Contexte.'
}
};
Context Objekt
Unser Kontext-Objekt wird ebenfalls erzeugt und ist zunächst absolut einfach.
const LocaleContext = createContext();
Provider Wrapper
Nun werden wir unseren Provider aufbauen, welcher die Übersetzungen zum Abrufen und den aktuellen Zustand bereitstellt. In diesen Provider werden wir später alle Components platzieren, welche von diesen Daten zu aktuell aktiver Übersetzung konsumieren sollen.
Wichtig: Nicht vergessen sollte die Prop-Eigenschaft children
, welche von React verwaltet wird und welche wir benötigen, um alle Kind-Komponenten innerhalb des LocaleProvider
Components (ja, ist im Grunde auch ein Component) auszugeben. Anders formuliert, beinhaltet children
alle Kind-Komponenten.
...
function LocaleProvider({ children }) {
// Define state - default 'de'
const [locale, setLocale] = useState('de');
/**
* Translate by given key
* ---
* @param {string} key - Translation key
* ---
* @return {string} - Translated value or the key
*/
const translate = (key) => {
return translations[locale][key] || key;
}
/**
* Change locale
* ---
* If there is a translation which
* matches 'newLocale', set it.
* ---
* @param {string} newLocale - New locale
*/
const changeLocale = (newLocale) => {
if (translations[newLocale]) {
setLocale((currentLocale) => newLocale);
}
};
// Context object values
const contextValue = {
locale,
translate,
changeLocale,
availableLocales: Object.keys(translations)
};
return (
<LocaleContext.Provider value={contextValue}>
{children}
</LocaleContext.Provider>
);
}
Was passiert in diesem Component? Wir setzen unsere Zustandsverwaltung mit einem initialen Wert de
.
Wir definieren die changeLocale
Funktion, welche die aktuell aktive Übersetzung an den übergebenen Wert ändert, wenn dieser im Übersetzungsobjekt verfügbar ist.
Wir bauen unser Context-Objekt für LocaleContext
zusammen. Initial ist das Objekt leer;
Wir geben den tatsächlichen Provider aus dem Context-Objekt LocaleContext
zurück und werfen alle künftigen Kind-Komponenten dazwischen.
Custom Hook
Um sicherzustellen, dass die Daten nur innerhalb von unserem Provider Wrapper verwendet werden, definieren wir eine Zwischen-Funktion, im Grunde einen eigenen Hook. Dieser Hook prüft, ob ein Kontext vorhanden ist.
Einen Kontext kann man mit der Funktion useContext
abrufen.
const context = useContext(MyContext);
Ein Kontext liegt uns erst vor, wenn wir einen Provider verwenden oder wenn es einen Provider gibt. In unserem Fall wird diese Funktion den Kontext aus LocaleContext
abrufen. Das unser tatsächlicher und einziger Kontext, welchen wir in unserem Beispiel erzeugen.
...
function useLocale() {
const context = useContext(LocaleContext);
if (context === undefined) {
// Hier Fehler werfen oder Warnung
}
return context;
}
Wenn also uns ein Kontext vorliegt, geben wir diesen einfach direkt zurück. Im abweichenden Fall können wir bestimmte Maßnahmen, wie Warnungen oder Abbruch der Ausführung definieren.
Somit ist diese Funktion eine Art Wächter, welcher uns garantiert, dass ein Kontext gegeben ist und uns diesen Kontext gibt. Ferner werden wir diesen Hook, statt useContext(LocaleContext)
verwenden.
Language Swicher
Nun definieren wir ein Component, welcher uns die Knöpfe (Buttons) zum Ändern der aktuellen Sprache bereitstellt und anzeigt, welcher Sprache aktuell aktiv ist.
...
function LangaugeSwicher() {
// Use context
const {
locale,
changeLocale,
availableLocales,
translate
} = useLocale();
return (
<div style={{ marginBottom: '20px' }}>
<p>{translate('currentLanguage')}: <strong>{locale.toUpperCase()}</strong></p>
<hr/>
<div>
{availableLocales.map(lang => (
<button
key={lang}
onClick={() => changeLocale(lang)}
style={{
margin: '5px',
padding: '8px 12px',
backgroundColor: locale === lang ? '#4caf50' : '#f0f0f0',
color: locale === lang ? 'white' : 'black',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
{lang.toUpperCase()}
</button>
))}
</div>
</div>
);
}
Wie etwas weiter oben erwähnt, verwenden wir useLocale()
Hook, um Elemente aus unserem Kontext-Objekt herauszuholen. Dafür ist folgender Block zuständig.
const {
locale,
changeLocale,
availableLocales,
translate
} = useLocale();
Das Gleiche, aber ohne Prüfung auf Vorhandensein des Kontexts, könnten wir wir folgt umsetzen.
const {
locale,
changeLocale,
availableLocales,
translate
} = useContext(LocaleContext);
Übersetzte Inhalte
Damit wir die Auswirkung der Übersetzungen sehen, benötigen wir irgendwelche Inhalte, die übersetzt werden. Dafür bauen wir ein Component LocalizedContent
auf. Dort werden wir einfach die translate
Funktion an jeder Stelle einsetzen, die übersetzt werden sollte.
...
function LocalizedContent() {
// Use context - Only 'translate' function
const { translate } = useLocale();
return (
<div style={{
padding: '20px',
border: '2px solid #ddd',
borderRadius: '14px',
backgroundColor: '#f9f9f9'
}}>
<h2>{translate('greeting')}</h2>
<p>{translate('description')}</p>
</div>
);
}
Mehr als übersetzte Inhalte auszugeben und ein paar Stil-Definitionen macht diese Funktion nicht. Wir werden dieses Component später in einem anderen Component einbinden.
Hauptinhalt
Eigentlich könnte man sich dieses Component sparen. Dieses Component stellt eine Überschrift bereit, deren Inhalt übersetzt wird und bindet andere beiden Components ein, LanguageSwitcher
und LocalizedContent
. Man könnte auch all das in ein Component packen. Das ist eine reine Geschmacksfrage und hat nichts mit Kontext-Verwendung zu tun.
...
function MainContent() {
// Use context - Only 'translate' function
const { translate } = useLocale();
return (
<div style={{ padding: '20px' }}>
<h1>{translate('welcome')}</h1>
<LangaugeSwicher />
<LocalizedContent />
</div>
);
}
Haupt Component
In diesem Component passiert etwas Wichtiges. Hier werden die unser MainContent
Component einbinden, welcher wieder den Rest einbindet. Dabei platzieren wir dieses Component zwischen unserem Custom Provider Wrapper. Das MainComponent
wird dann weiter im Verlauf über die Prop-Eigenschaft children
zwischen LocaleContext.Provider
platziert.
...
function CustomLocalizer() {
return (
<LocaleProvider>
<MainContent />
</LocaleProvider>
);
}
Damit haben wir unser Beispiel vollständig aufgebaut und das Ergebnis sieht wie folgt aus.
Gesamtes Beispiel
Nun fügen wir alles Zusammen und exportieren unsere Haupt-Komponente, sodass diese an einer beliebigen anderen Stelle verwendet werden kann.
...
// React
import { createContext, useContext, useState } from 'react';
const translations = {
de: {
welcome: 'Willkommen',
changeLanguage: 'Sprache ändern',
currentLanguage: 'Aktuelle Sprache',
greeting: 'Hallo React',
description: 'Das ist ein Beispiel für die Verwendung von Context.'
},
en: {
welcome: 'Welcome',
changeLanguage: 'Change language',
currentLanguage: 'Current language',
greeting: 'Hello React',
description: 'This is an example of usage of context.'
},
fr: {
welcome: 'Bienvenue',
changeLanguage: 'Changer de langue',
currentLanguage: 'Langue actuelle',
greeting: 'Bonjour React',
description: 'Ceci est un exemple d\'utilisation du Contexte.'
}
};
const LocaleContext = createContext();
console.log('Init LocaleContext', LocaleContext);
function LocaleProvider({ children }) {
const [locale, setLocale] = useState('de');
const translate = (key) => {
return translations[locale][key] || key;
};
const changeLocale = (newLocale) => {
if (translations[newLocale]) {
setLocale((currentLocale) => newLocale);
}
};
// Context value
const value = {
locale,
translate,
changeLocale,
availableLocales: Object.keys(translations)
};
return (
<LocaleContext.Provider value={value}>
{children}
</LocaleContext.Provider>
);
}
// Custom Hook
function useLocale() {
const context = useContext(LocaleContext);
if (context === undefined) {
throw new Error('useLocale muss innerhalb eines LocaleProviders verwendet werden.');
}
return context;
}
function LangaugeSwicher() {
// Use context
const {
locale,
changeLocale,
availableLocales,
translate
} = useLocale();
return (
<div style={{ marginBottom: '20px' }}>
<p>{translate('currentLanguage')}: <strong>{locale.toUpperCase()}</strong></p>
<hr/>
<div>
{availableLocales.map(lang => (
<button
key={lang}
onClick={() => changeLocale(lang)}
style={{
margin: '5px',
padding: '8px 12px',
backgroundColor: locale === lang ? '#4caf50' : '#f0f0f0',
color: locale === lang ? 'white' : 'black',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
{lang.toUpperCase()}
</button>
))}
</div>
</div>
);
}
function LocalizedContent() {
// Use context - Only 'translate' function
const { translate } = useLocale();
return (
<div style={{
padding: '20px',
border: '2px solid #ddd',
borderRadius: '14px',
backgroundColor: '#f9f9f9'
}}>
<h2>{translate('greeting')}</h2>
<p>{translate('description')}</p>
</div>
);
}
function MainContent() {
// Use context - Only 'translate' function
const { translate } = useLocale();
return (
<div style={{ padding: '20px' }}>
<h1>{translate('welcome')}</h1>
<LangaugeSwicher />
<LocalizedContent />
</div>
);
}
function CustomLocalizer() {
return (
<LocaleProvider>
<MainContent />
</LocaleProvider>
);
}
export default CustomLocalizer;