Skip to content

[ FEATURE ] Resolve issue with color scheme for web #383

@EliasGit117

Description

@EliasGit117

Web color scheme is broken.
To make a working color scheme for web version of app without breaking native version, it requires to write an ugly work around like this:

// app/providers/ThemeProvider.tsx
import { DarkTheme, DefaultTheme, Theme, ThemeProvider as NavigationThemeProvider } from '@react-navigation/native';
import { StatusBar } from 'expo-status-bar';
import { Appearance, ColorSchemeName, Platform } from 'react-native';
import { NAV_THEME } from '~/lib/constants';
import { useColorScheme } from '~/lib/useColorScheme';
import { setAndroidNavigationBar } from '~/lib/android-navigation-bar';
import { isWeb } from '~/lib/utils';
import { ReactNode, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useThemeStore, Theme as StoreTheme } from '~/stores/theme-store';
import { SplashScreen } from 'expo-router';
import * as React from 'react';

SplashScreen.preventAutoHideAsync();

export function ThemeProvider({ children }: { children: ReactNode }) {
  const { theme } = useThemeStore();
  const hasMounted = React.useRef(false);
  const { colorScheme, isDarkColorScheme, setColorScheme } = useColorScheme();
  const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false);

  useIsomorphicLayoutEffect(() => {
    if (hasMounted.current) {
      return;
    }

    if (isWeb) {
      // Adds the background color to the html element to prevent white background on overscroll.
      document.documentElement.classList.add('bg-background');
    }

    //This a hack to set color scheme from theme store, without setTimout it doesn't work
    // TODO: Need to find a better way to handle this.
    setTimeout(() => {
      setColorScheme(theme);
      setIsColorSchemeLoaded(true);
      hasMounted.current = true;

      if (!isWeb || theme !== 'system')
        return;

      handleColorSchemeChange(colorScheme, theme);
    }, 10);
  }, []);

  useEffect(() => {
    handleColorSchemeChange(colorScheme, theme);
  }, [colorScheme]);

  useEffect(() => {
    if (!isColorSchemeLoaded || !hasMounted.current)
      return;

    // This is a hack to prevent the splash screen from auto-hiding before asset loading is complete.
    // TODO: Need to find a better way to handle this.
    const timeout = setTimeout(() => SplashScreen.hideAsync(), 10);
    return () => clearTimeout(timeout);
  }, [isColorSchemeLoaded]);

  if (!isColorSchemeLoaded)
    return null;

  return (
    <NavigationThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
      <StatusBar style={isDarkColorScheme ? 'light' : 'dark'}/>
      {children}
    </NavigationThemeProvider>
  );
}

const LIGHT_THEME: Theme = { ...DefaultTheme, colors: NAV_THEME.light };
const DARK_THEME: Theme = { ...DarkTheme, colors: NAV_THEME.dark };

const handleColorSchemeChange = (colorScheme: 'light' | 'dark', theme: StoreTheme) => {
  setAndroidNavigationBar(colorScheme).then();

  if (!isWeb || theme !== 'system')
    return;

  if (colorScheme === 'dark')
    document.documentElement.classList.add('dark');
  else if (colorScheme === 'light')
    document.documentElement.classList.remove('dark');
};

const useIsomorphicLayoutEffect =
  Platform.OS === 'web' && typeof window === 'undefined' ? useEffect : useLayoutEffect;


If anyone have any better idea, I would be glad to see it, because I can't find a better implementation but in the same time I don't like my variant

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions