I have the next component:
const App = observer(({injector}: Props) => {
const preferences = injector.getPreferences().execute()
const themeStore = useLocalStore(() => ({
_theme:
preferences.theme === ETheme.DARK
? CombinedDarkTheme
: CombinedDefaultTheme,
get theme(): ReactNativePaper.Theme & Theme {
return this._theme
},
set theme(value: ReactNativePaper.Theme & Theme) {
this._theme = value
},
}))
const onThemeChange = React.useCallback(() => {
const colorScheme = Appearance.getColorScheme()
themeStore.theme =
colorScheme === 'dark' ? CombinedDarkTheme : CombinedDefaultTheme
}, [themeStore])
React.useEffect(() => {
if (preferences.theme === ETheme.SYSTEM) {
const subscription = Appearance.addChangeListener(onThemeChange)
return subscription.remove
}
}, [onThemeChange, preferences.theme])
return (
<PaperProvider theme={themeStore.theme}>
<NavigationContainer theme={themeStore.theme}>
// Some views
</NavigationContainer>
</PaperProvider>
)
})
Global MobX store:
export default class PreferencesGateway implements IPreferencesGateway {
_preferences: Preferences = {
theme: ETheme.LIGHT,
signedInUserId: null,
}
constructor(private _storage: IStorageGateway) {
makeObservable(this, {
_preferences: observable,
preferences: computed,
setPreferences: action,
})
}
get preferences(): Preferences {
return this._preferences
}
async setPreferences({
theme,
signedInUserId,
}: PreferencesUpdate): Promise<void> {
if (theme) this._preferences.theme = theme
if (signedInUserId !== undefined)
this._preferences.signedInUserId = signedInUserId
await this.savePersistData()
}
async loadPersistData(): Promise<Preferences> {
const prefs = await this._storage.get<Preferences>(STORAGE_KEY_PREFERENCES)
if (prefs) this._preferences = prefs
return this._preferences
}
async savePersistData(): Promise<void> {
await this._storage.set(STORAGE_KEY_PREFERENCES, this._preferences)
}
}
I need to App
listened MobX global store (preferences.theme
), the ETheme
definition is below:
enum ETheme {
DARK = 'DARK',
LIGHT = 'LIGHT',
SYSTEM = 'SYSTEM',
}
When the user sets preference.theme
to DARK
or LIGHT
, the app just follows a simple expression:
theme = preferences.theme === ETheme.DARK ? ETheme.DARK : ETheme.LIGHT
However, when user sets preference.theme
to AUTO
, the app has to subscribe on Appearance.addChangeListener()
and follows a system theme.
Unfortunately, my solution doesn't work correctly and leads to multiple changes when the user sets SYSTEM
. I am a noobie in React Native and MobX and can't get an error reason but seems these stores conflict. I've also tried useState
instead of useLocalStore
but it just led to infinite redrawing.
How to fix it?
P.S. I feel that I've written a some bul****, and it can be simplified also.
UPD
I simplified App
a little:
const App = observer(({injector}: Props) => {
const preferences = injector.getPreferences().execute()
const [systemTheme, setSystemTheme] = React.useState<
ReactNativePaper.Theme & Theme
>(
Appearance.getColorScheme() === 'dark'
? CombinedDarkTheme
: CombinedDefaultTheme,
)
const onThemeChange = React.useCallback(() => {
const colorScheme = Appearance.getColorScheme()
setSystemTheme(
colorScheme === 'dark' ? CombinedDarkTheme : CombinedDefaultTheme,
)
}, [])
React.useEffect(() => {
if (preferences.theme === ETheme.SYSTEM) {
const subscription = Appearance.addChangeListener(onThemeChange)
return subscription.remove
}
}, [onThemeChange, preferences.theme])
const localTheme =
preferences.theme === ETheme.DARK ? CombinedDarkTheme : CombinedDefaultTheme
const theme = preferences.theme === ETheme.SYSTEM ? systemTheme : localTheme
return (
<PaperProvider theme={theme}>
<NavigationContainer theme={theme}>
<Stack.Navigator initialRouteName="SplashScreen">
<Stack.Screen name="MainScreen" options={{headerShown: false}}>
{props => <MainScreen injector={injector} {...props} />}
</Stack.Screen>
<Stack.Screen
name="SettingsScreen"
options={{
header: props => <SimpleAppBar {...props} title="Settings" />,
}}>
{props => <SettingsScreen injector={injector} {...props} />}
</Stack.Screen>
<Stack.Screen
name="SignInScreen"
options={{
header: props => <SimpleAppBar {...props} title="Account" />,
}}>
{props => <SignInScreen injector={injector} {...props} />}
</Stack.Screen>
<Stack.Screen
name="SignUpScreen"
options={{
header: props => <SimpleAppBar {...props} title="New account" />,
}}>
{props => <SignUpScreen injector={injector} {...props} />}
</Stack.Screen>
<Stack.Screen name="SplashScreen" options={{headerShown: false}}>
{props => <SplashScreen injector={injector} {...props} />}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
</PaperProvider>
)
})
However, I got a new error when I try to switch a theme from SYSTEM
to LIGHT
or DARK
: