navigation Navigation


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.

    CustomLocalizer.jsx
    // 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.

    CustomLocalizer.jsx
    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.

    CustomLocalizer.jsx
    ...
    
    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.

    CustomLocalizer.jsx
    ...
    
    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.

    CustomLocalizer.jsx
    ...
    
    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.

    CustomLocalizer.jsx
    ...
    
    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.

    CustomLocalizer.jsx
    ...
    
    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.

    CustomLocalizer.jsx
    ...
    
    function CustomLocalizer() {
        return (
            <LocaleProvider>
                <MainContent />
            </LocaleProvider>
        );
    }

    Damit haben wir unser Beispiel vollständig aufgebaut und das Ergebnis sieht wie folgt aus.

    React Context - Custom Provider Wrapper - Beispiel Ergebnis


    Gesamtes Beispiel

    Nun fügen wir alles Zusammen und exportieren unsere Haupt-Komponente, sodass diese an einer beliebigen anderen Stelle verwendet werden kann.

    CustomLocalizer.jsx
    ...
    
    // 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;