Search code examples
javascriptreactjsreact-nativereact-navigation-v5

React-navigation switch theme toggle


i've implemented theming support into my app with react-navigation, as you can see below. i am using the system theme settings, and my app follows this rule this is working great, but there's one thing left on my to-do list, a option to toggle light/dark theme inside my app, keep this selection, and store it into the user defaults or something like that..

i followed the official docs (https://reactnavigation.org/docs/themes/) but they don't mention how to switch themes manually. in my testing i always got a message that the theme prop is read only and cannot be changed manually. so how to do that? any help'd be greatly appreciated ;)

App.js

import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { AppearanceProvider, useColorScheme, useTheme } from 'react-native-appearance';

const Stack = createStackNavigator();

// ------------------App-----------------------------
export default function App() {

    const scheme = useColorScheme();

    const MyDarkTheme = {
    dark: true,
    ...},
    const LightTheme = {
    dark: false,
    ...}
    
    return (
      <AppearanceProvider>
        <NavigationContainer theme={scheme === "dark" ? MyDarkTheme : LightTheme}>
          <Stack.Navigator>
          <Stack.Screen>
            name="home"
          ...
          <Stack.Screen
            name="Settings"
            component={Settings}
            />
          </Stack.Navigator>
        </NavigationContainer>
      </AppearanceProvider>
    );
  }

in my Components:

import React, { useState, useEffect} from 'react';
import { Card } from 'react-native-elements';
import { useTheme} from '@react-navigation/native';

function CardOne(props) {

    const { colors } = useTheme(); // works
    const theme = useTheme();

return (
        <Card containerStyle={{backgroundColor: colors.cardBackgroundColor, borderColor: colors.borderColor, borderWidth: 2, borderRadius: 5}}>
        ...
        </Card>
        );
}  
export default CardOne;

i really someone can help me out ;)


Solution

  • You can use the Context and do something like below, basically maintain the theme in state at App.js and update value via context.

    export const ThemeContext = React.createContext();
    
    export default function App() {
      const [theme, setTheme] = useState('Light');
    
      const themeData = { theme, setTheme };
      return (
        <ThemeContext.Provider value={themeData}>
          <NavigationContainer theme={theme == 'Light' ? DefaultTheme : DarkTheme}>
            <Drawer.Navigator initialRouteName="Root">
              <Drawer.Screen name="Home" component={HomeScreen} />
              <Drawer.Screen name="Root" component={Root} />
            </Drawer.Navigator>
          </NavigationContainer>
        </ThemeContext.Provider>
      );
    }
    

    You can switch the theme from a screen like below

    function ProfileScreen({ navigation }) {
      const { setTheme, theme } = React.useContext(ThemeContext);
      return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
          <Text>Profile Screen</Text>
          <Button
            title="Switch Theme"
            onPress={() => setTheme(theme === 'Light' ? 'Dark' : 'Light')}
          />
        </View>
      );
    }
    

    Sample code https://snack.expo.io/@guruparan/5b84d0