I am making a button in React Native which should allow the user to change between a light and dark theme:
I have wrapped my app in a context:AppSettingsContext.tsx
import {fetchThemeFromStorage} from "../../helpers/_appLoadFetch";import React, {createContext, useState, useEffect} from "react";import { AppSettingsContextStruct, Currencies, Themes } from "../../types";const defaultVal: AppSettingsContextStruct = { theme: Themes.LIGHT, setTheme: () => {},}type CtxProps = { children: JSX.Element}const AppSettingsContext = createContext(defaultVal);const AppSettingsContextProvider:React.FC<CtxProps> = (props) => { const [theme, setTheme] = useState<Themes>(Themes.LIGHT); let storageTheme = fetchThemeFromStorage(); useEffect(()=>{ const asyncWrap = async () => { return await storageTheme; }; asyncWrap().then((val) => { setTheme(val); }) },[storageTheme]) return (<AppSettingsContext.Provider value={{theme,setTheme,currency,setCurrency,notifications,setNotifications}}> {props.children}</AppSettingsContext.Provider> )}export {AppSettingsContext, AppSettingsContextProvider}
Which imports the following function:_appLoadFetch.ts
export const fetchThemeFromStorage = async () => { return AsyncStorage.getItem("THEME") .then((value) => { if ( value === null ) { return Themes.LIGHT } return value === `"LIGHT"` ? Themes.LIGHT : Themes.DARK; }) .catch((err) => { console.log(err); return Themes.LIGHT; });};
I then have my button which should allow the user to change the theme:ChangeTheme.tsx
import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';import { useEffect, useState } from 'react';import { TouchableOpacity, Text, StyleSheet, View } from "react-native"import { useAppSettingsContext } from '../../hooks/useAppSettingsContext';import { Themes } from '../../types';import AsyncStorage from '@react-native-async-storage/async-storage';const getIsLight = async (theme: Themes) => { if (theme === Themes.LIGHT) { return true } if (theme === Themes.DARK) { return false } console.log("error") return false;} const SettingsToggleComponent = () => { const { theme, setTheme } = useAppSettingsContext(); const [isLight, setIsLight] = useState<boolean>(true); let isLightInitial = getIsLight(theme); useEffect(() => { const asyncWrap = async () => { return await isLightInitial; }; asyncWrap().then((val) => { setIsLight(val); }) }, [isLightInitial]) return (<TouchableOpacity onPress={() => { if (theme === Themes.LIGHT) { AsyncStorage.setItem("THEME", JSON.stringify(Themes.DARK)) setTheme(Themes.DARK) setIsLight(false) } else { AsyncStorage.setItem("THEME", JSON.stringify(Themes.LIGHT)) setTheme(Themes.LIGHT) setIsLight(true) } }}><View style={styles.settingLayout}> {/* Make sure that the possible names for the icons are used instead of random icon names */}<View style={styles.iconContainer}><MaterialCommunityIcons name="theme-light-dark" size={24} color="#5a5a5a" /></View><View style={styles.textContainer}><View style={styles.textContainerLayout}><Text style={styles.settingName}>{settingName}</Text><View style={styles.arrowSettingValueContainer}> { isLight ?<Ionicons name="ios-sunny-sharp" size={24} color="#5A5A5A" /> : <Ionicons name="ios-moon-sharp" size={24} color="#5A5A5A" /> }</View></View></View></View></TouchableOpacity> )}
This will change the theme but when I refresh the app (using expo), it seems to rerun the fetchThemeFromStorage function a few times, resetting the theme back to Themes.LIGHT in the process as it looks like AppSettingsContextProvider is re-rendered 4 times after reloading.
I'm relatively new to React/React Native so I don't really know why this would happen.
EDIT:
The multiple re-renders only happen when I change the theme to Themes.DARK, if it is Themes.LIGHT when I reload the component will not re-render after the initial render, which is the behaviour I expect.