admin管理员组

文章数量:1289857

I've been working with react native elements. I want to implement a dark mode to my app but for some reason I cant get the theme prop in <ThemeProvider/> to change when my state in my context changes.

Here is my context where I have my darkTheme and lightTheme object. I also have a lightThemeState using useState so I can set that state from a child ponent.

import React, { createContext, useState, useEffect } from "react";
import { AsyncStorage } from "react-native";

import { ThemeProvider } from "react-native-elements";
import lightTheme from "../themes/light";
import darkTheme from "../themes/dark";

export const ThemeModeContext = createContext();

export const ThemeContextProvider = (props) => {
  const [lightThemeState, setLightThemeState] = useState(true);

  const saveThemeState = async () => {
    if (lightThemeState) {
      await AsyncStorage.removeItem("lightThemeState");
    } else {
      await AsyncStorage.setItem(
        "lightThemeState",
        JSON.stringify(lightThemeState)
      );
    }
  };

  const getThemeState = async () => {
    currentMode = await AsyncStorage.getItem("lightThemeState");

    if (currentMode) {
      setLightThemeState(JSON.parse(currentMode));
    }
  };

  useEffect(() => {
    saveThemeState();
  }, [lightThemeState]);

  useEffect(() => {
    getThemeState();
  }, []);

  const currentTheme = lightThemeState ? lightTheme : darkTheme;

  console.log("LIGHT THEME STATE", lightThemeState); 
// When I log this after I used the setLigthThemeState in a child ponent. It gives the correct state ie true or false.
  console.log("COLOR OF THE THEMES BACKGROUND", currentTheme.colors.background);
// This also gives the correct background for the theme that is the "currentTheme" depending on the state. So this far, everything is correct.

  return (
    <ThemeModeContext.Provider value={[lightThemeState, setLightThemeState]}>
      <ThemeProvider theme={currentTheme}>{props.children}</ThemeProvider>
    </ThemeModeContext.Provider>
  );
};

export default ThemeContextProvider;

Because I have another context that I use for other logic. I bine <ThemeContextProvider/> with my other context <JourneyContextProvider/>. Like so:

import React from "react";
import ThemeContextProvider from "./themeStore";
import JourneyContextProvider from "./journeyStore";

export const CombinedStoreProvider = (props) => {
  return (
    <JourneyContextProvider>
      <ThemeContextProvider>{props.children}</ThemeContextProvider>
    </JourneyContextProvider>
  );
};

export default CombinedStoreProvider;

Then finally i wrap the whole app in my <CombinedStoreProvider/>. Like so.

import React from "react";
import { SafeAreaView } from "react-native";

import { createAppContainer, createSwitchNavigator } from "react-navigation";
import { createMaterialBottomTabNavigator } from "react-navigation-material-bottom-tabs";

import Icon from "react-native-vector-icons/Ionicons";

import MoreScreenfrom "./src/screens/MoreModal";
import CombinedStoreProvider from "./store/binedStore";

const TabNavigator = createMaterialBottomTabNavigator(
  {
    MoreScreen: {
      screen: MoreScreen,
      navigationOptions: {
        title: "More",
        tabBarIcon: ({ tintColor }) => (
          <SafeAreaView>
            <Icon style={[{ color: tintColor }]} size={25} name={"ios-more"} />
          </SafeAreaView>
        ),
      },
    },
  },
  {
    theme: ({ darkTheme }) => console.log(darkTheme),
    barStyleDark: {
      backgroundColor: darkTheme.colors.background,
    },
    barStyleLight: {
      backgroundColor: lightTheme.colors.background,
    },
    shifting: false,
    labeled: true,
    initialRouteName: "MoreScreen",
    activeColor: "#E4DC93",
    inactiveColor: "#fff",
    barStyle: { backgroundColor: "transparent", height: 80, paddingTop: 10 },
  }
);

const AllRoutes = createSwitchNavigator(
  {
    PersonalSettings: {
      title: "Personal Settings",
      screen: PersonalSettings,
      header: ({ goBack }) => ({
        left: (
          <Icon
            name={"chevron-left"}
            onPress={() => {
              goBack();
            }}
          />
        ),
      }),
    },
    Tabs: {
      screen: TabNavigator,
    },
  },
  {
    initialRouteName: "Tabs",
  }
);

const AppContainer = createAppContainer(AllRoutes);

export default App = () => {
  return (
    <CombinedStoreProvider>
      <AppContainer />
    </CombinedStoreProvider>
  );
};

And here is my child ponent where I toggle the lightThemeState in my context. But even though everything looks great in ThemeContextProvider (I console log the state and background color and they have succesfully changed the theme). But in this ponent I only get the previous theme. Like nothing changed even though this child ponent rerenders when I toggle the lightThemeState. I know this because the console log in this ponent logs again after i toggle the theme but the logs show the previous theme colors. Here is the child ponent:

import React, { useContext, useState } from "react";
import { StyleSheet, View, Text } from "react-native";
import { LayoutView, ContainerView } from "../../ponents/styles";
import { ThemeModeContext } from "../../../store/themeStore";
import { Card, ListItem, Avatar, ThemeContext } from "react-native-elements";

import CustomButton from "../../ponents/CustomButton";

const INITIAL_PERSONAL_INFO_STATE = {
  name: "",
  username: "",
  profileImage: "",
  favoriteDestinations: [],
};

const MoreModal = (props) => {
  const [personalInfo, setPersonalInfo] = useState(INITIAL_PERSONAL_INFO_STATE);

  const [lightThemeState, setLightThemeState] = useContext(ThemeModeContext);
  const { theme } = useContext(ThemeContext);
  const { navigate } = props.navigation;

  const primaryColor = theme.colors.background;

  console.log("COLOR IN COMPONENT", primaryColor);
// The color is from the previous theme and even thou the state has changed in the state below
  console.log("LIGHT THEME STATE IN COMPONENT", lightThemeState);

  return (
    <LayoutView primaryColor={theme.colors.background}>
      <ContainerView>
        <View>
        </View>
        <Card
          title={"Settings"}
        >
          <ListItem
            title="Light mode"
            switch={{
              value: lightThemeState,
              onValueChange: (value) => setLightThemeState(value), 
// Here is where I set lighThemeState to false in my context

            }}
            bottomDivider
        </Card>
      </ContainerView>
      <CustomButton title={"Sign in"}></CustomButton>
    </LayoutView>
  );
};

export default MoreModal;

Maybe there is something wrong with the darkTheme and lightTheme you ask? No, if I changs the state from true to false and reload the app. It works. Somehow the theme doesnt update in the <ThemeProvider theme={currentTheme}/>. Can someone explain why?

I've been working with react native elements. I want to implement a dark mode to my app but for some reason I cant get the theme prop in <ThemeProvider/> to change when my state in my context changes.

Here is my context where I have my darkTheme and lightTheme object. I also have a lightThemeState using useState so I can set that state from a child ponent.

import React, { createContext, useState, useEffect } from "react";
import { AsyncStorage } from "react-native";

import { ThemeProvider } from "react-native-elements";
import lightTheme from "../themes/light";
import darkTheme from "../themes/dark";

export const ThemeModeContext = createContext();

export const ThemeContextProvider = (props) => {
  const [lightThemeState, setLightThemeState] = useState(true);

  const saveThemeState = async () => {
    if (lightThemeState) {
      await AsyncStorage.removeItem("lightThemeState");
    } else {
      await AsyncStorage.setItem(
        "lightThemeState",
        JSON.stringify(lightThemeState)
      );
    }
  };

  const getThemeState = async () => {
    currentMode = await AsyncStorage.getItem("lightThemeState");

    if (currentMode) {
      setLightThemeState(JSON.parse(currentMode));
    }
  };

  useEffect(() => {
    saveThemeState();
  }, [lightThemeState]);

  useEffect(() => {
    getThemeState();
  }, []);

  const currentTheme = lightThemeState ? lightTheme : darkTheme;

  console.log("LIGHT THEME STATE", lightThemeState); 
// When I log this after I used the setLigthThemeState in a child ponent. It gives the correct state ie true or false.
  console.log("COLOR OF THE THEMES BACKGROUND", currentTheme.colors.background);
// This also gives the correct background for the theme that is the "currentTheme" depending on the state. So this far, everything is correct.

  return (
    <ThemeModeContext.Provider value={[lightThemeState, setLightThemeState]}>
      <ThemeProvider theme={currentTheme}>{props.children}</ThemeProvider>
    </ThemeModeContext.Provider>
  );
};

export default ThemeContextProvider;

Because I have another context that I use for other logic. I bine <ThemeContextProvider/> with my other context <JourneyContextProvider/>. Like so:

import React from "react";
import ThemeContextProvider from "./themeStore";
import JourneyContextProvider from "./journeyStore";

export const CombinedStoreProvider = (props) => {
  return (
    <JourneyContextProvider>
      <ThemeContextProvider>{props.children}</ThemeContextProvider>
    </JourneyContextProvider>
  );
};

export default CombinedStoreProvider;

Then finally i wrap the whole app in my <CombinedStoreProvider/>. Like so.

import React from "react";
import { SafeAreaView } from "react-native";

import { createAppContainer, createSwitchNavigator } from "react-navigation";
import { createMaterialBottomTabNavigator } from "react-navigation-material-bottom-tabs";

import Icon from "react-native-vector-icons/Ionicons";

import MoreScreenfrom "./src/screens/MoreModal";
import CombinedStoreProvider from "./store/binedStore";

const TabNavigator = createMaterialBottomTabNavigator(
  {
    MoreScreen: {
      screen: MoreScreen,
      navigationOptions: {
        title: "More",
        tabBarIcon: ({ tintColor }) => (
          <SafeAreaView>
            <Icon style={[{ color: tintColor }]} size={25} name={"ios-more"} />
          </SafeAreaView>
        ),
      },
    },
  },
  {
    theme: ({ darkTheme }) => console.log(darkTheme),
    barStyleDark: {
      backgroundColor: darkTheme.colors.background,
    },
    barStyleLight: {
      backgroundColor: lightTheme.colors.background,
    },
    shifting: false,
    labeled: true,
    initialRouteName: "MoreScreen",
    activeColor: "#E4DC93",
    inactiveColor: "#fff",
    barStyle: { backgroundColor: "transparent", height: 80, paddingTop: 10 },
  }
);

const AllRoutes = createSwitchNavigator(
  {
    PersonalSettings: {
      title: "Personal Settings",
      screen: PersonalSettings,
      header: ({ goBack }) => ({
        left: (
          <Icon
            name={"chevron-left"}
            onPress={() => {
              goBack();
            }}
          />
        ),
      }),
    },
    Tabs: {
      screen: TabNavigator,
    },
  },
  {
    initialRouteName: "Tabs",
  }
);

const AppContainer = createAppContainer(AllRoutes);

export default App = () => {
  return (
    <CombinedStoreProvider>
      <AppContainer />
    </CombinedStoreProvider>
  );
};

And here is my child ponent where I toggle the lightThemeState in my context. But even though everything looks great in ThemeContextProvider (I console log the state and background color and they have succesfully changed the theme). But in this ponent I only get the previous theme. Like nothing changed even though this child ponent rerenders when I toggle the lightThemeState. I know this because the console log in this ponent logs again after i toggle the theme but the logs show the previous theme colors. Here is the child ponent:

import React, { useContext, useState } from "react";
import { StyleSheet, View, Text } from "react-native";
import { LayoutView, ContainerView } from "../../ponents/styles";
import { ThemeModeContext } from "../../../store/themeStore";
import { Card, ListItem, Avatar, ThemeContext } from "react-native-elements";

import CustomButton from "../../ponents/CustomButton";

const INITIAL_PERSONAL_INFO_STATE = {
  name: "",
  username: "",
  profileImage: "",
  favoriteDestinations: [],
};

const MoreModal = (props) => {
  const [personalInfo, setPersonalInfo] = useState(INITIAL_PERSONAL_INFO_STATE);

  const [lightThemeState, setLightThemeState] = useContext(ThemeModeContext);
  const { theme } = useContext(ThemeContext);
  const { navigate } = props.navigation;

  const primaryColor = theme.colors.background;

  console.log("COLOR IN COMPONENT", primaryColor);
// The color is from the previous theme and even thou the state has changed in the state below
  console.log("LIGHT THEME STATE IN COMPONENT", lightThemeState);

  return (
    <LayoutView primaryColor={theme.colors.background}>
      <ContainerView>
        <View>
        </View>
        <Card
          title={"Settings"}
        >
          <ListItem
            title="Light mode"
            switch={{
              value: lightThemeState,
              onValueChange: (value) => setLightThemeState(value), 
// Here is where I set lighThemeState to false in my context

            }}
            bottomDivider
        </Card>
      </ContainerView>
      <CustomButton title={"Sign in"}></CustomButton>
    </LayoutView>
  );
};

export default MoreModal;

Maybe there is something wrong with the darkTheme and lightTheme you ask? No, if I changs the state from true to false and reload the app. It works. Somehow the theme doesnt update in the <ThemeProvider theme={currentTheme}/>. Can someone explain why?

Share Improve this question edited Nov 1, 2021 at 7:16 Lance U. Matthews 16.6k6 gold badges51 silver badges71 bronze badges asked Apr 18, 2020 at 19:17 KulioKulio 1451 gold badge1 silver badge11 bronze badges
Add a ment  | 

5 Answers 5

Reset to default 4

You can't change the theme dynamically with React Native Elements. Unfortunately this isn't documented anywhere - it's an important point since most users of RNE will assume they can change the entire theme dynamically during runtime (well, I did).

There's a couple of closed issues on React Native Elements github that mention this. For example this issue (Jan 2019) one of the devs stated:

Currently right now this isn't possible. The ThemeProvider doesn't allow runtime changes to it's props. This is because changes to the props of the ThemeProvider will trigger a rerender for all ponents under the tree.

UPDATE: You can change the theme dynamically, but you have to use the withTheme HOC that React Native Elements provides (e.g. calling the updateTheme function provided by withTheme). There's a little bit of extra wiring but it's doable. You can wrap the HOC at near the top level so the theme update propagates to all children.

Exacty that. Just wanted to add that its also possible to use useContext.

If you wrap you to level ponent with a provider and then use the useContext hook in the child ponent where you want to be able to change the theme, you can extract the updateTheme like so:

const { theme, updateTheme } = useContext(ThemeContext);

Its the same answer as starlabs but another way to do it and the way that I did ut.

so as to work with react-native-elements I tried to update it dynamically but as its specified upstair the only way to update it is this way, its been the first time I've been forced to use a Consumer in this way XD:

//App.js
import 'react-native-gesture-handler';
import React from "react";
import { Provider } from "react-redux";
import { store } from "./redux";
import { TranslationProvider, ColorProvider } from "./context";
import { ThemeProvider } from "react-native-elements";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { appTheme as theme } from './theme';
import App from "./navigation";
import ThemeConsumer from "./ponents/consumers/themeConsumer";

    export default () =>
        <Provider store={store}>
            <SafeAreaProvider>
                <TranslationProvider>
                    <ColorProvider>
                        <ThemeProvider theme={theme}>
                            <ThemeConsumer>
                                <App />
                            </ThemeConsumer>
                        </ThemeProvider>
                    </ColorProvider>
                </TranslationProvider>
            </SafeAreaProvider>
        </Provider>
    
    

//themeConsumer.js

import React, { useContext, useEffect } from 'react';
import { ThemeContext } from "react-native-elements";
import { color } from '../../colors';
import { useTheme } from '../../context';

export default ({ children }) => {
    const { updateTheme } = useContext(ThemeContext);
    const { isDarkMode } = useTheme();
    useEffect(() => {
        updateTheme({ colors: color });
    }, [isDarkMode()])
    return (
        <>
            {children}
        </>
    )
}

You can update the theme colors by just toggling useDark on the Provider. This is a bit of a hack but seems to be working just fine.

I'm a bit surprised that if it's so easy to get an update this way there seems to be no other way in the library currently to get a theme update without the HOC which is an old design pattern.

Change Theme in React - Native in just fews steps.

  1. First Add toggle button in your ponent
  2. Create Action and Reducer to Save theme in reducer.
  3. Then get theme name which you have saved in reducer using useSelector.
  4. Then in index.js file set theme according to select theme. <NavigationContainer theme={mode == 'light' ? theme.light :[Here is example][1] theme.dark}> {user ? <AppNavigator /> : <AuthNavigator />} </NavigationContainer>

本文标签: javascriptchanging theme with react native elements not workingStack Overflow