react-native-web icon indicating copy to clipboard operation
react-native-web copied to clipboard

Appearance.setColorScheme not workin in RN Web

Open NewCoder2023 opened this issue 1 year ago • 6 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Describe the issue

Appearance.setColorScheme("dark") works perfectly fine for iOS and Android but raises the following Error when used for RN web even thought its written in the documentation that it works for web.

_Appearance.default.setColorScheme is not a function. (In '_Appearance.default.setColorScheme("dark")', '_Appearance.default.setColorScheme' is undefined)

Expected behavior

It should change the colorScheme.

Steps to reproduce

1.Create a Pressable 2. onPress={Appearance.setColorScheme("dark")} 3. Observe error in RN web

Test case

https://snack.expo.dev/@hadi_ea/setcolorscheme-error

Additional comments

Error:

Uncaught Error _Appearance.default.setColorScheme is not a function. (In '_Appearance.default.setColorScheme("dark")', '_Appearance.default.setColorScheme' is undefined) Call Stack

Source Map This call stack is not symbolicated. Some features are unavailable such as viewing the function name or tapping to open files. index http://localhost:8081/node_modules/expo-router/entry.bundle:153573:52 renderWithHooks http://localhost:8081/node_modules/expo-router/entry.bundle:25695:33 mountIndeterminateComponent http://localhost:8081/node_modules/expo-router/entry.bundle:28984:34 beginWork$1 http://localhost:8081/node_modules/expo-router/entry.bundle:35239:29 performUnitOfWork http://localhost:8081/node_modules/expo-router/entry.bundle:34509:29 workLoopSync http://localhost:8081/node_modules/expo-router/entry.bundle:34432:28 renderRootSync http://localhost:8081/node_modules/expo-router/entry.bundle:34405:25 recoverFromConcurrentError http://localhost:8081/node_modules/expo-router/entry.bundle:33897:40 performConcurrentWorkOnRoot http://localhost:8081/node_modules/expo-router/entry.bundle:33810:54 workLoop http://localhost:8081/node_modules/expo-router/entry.bundle:37546:48 flushWork http://localhost:8081/node_modules/expo-router/entry.bundle:37524:28 performWorkUntilDeadline http://localhost:8081/node_modules/expo-router/entry.bundle:37761:48 Showing all frames Component Stack

Source Map This call stack is not symbolicated. Some features are unavailable such as viewing the function name or tapping to open files. index http://localhost:8081/node_modules/expo-router/entry.bundle:153566:43 Route http://localhost:8081/node_modules/expo-router/entry.bundle:65953:44 Route http://localhost:8081/node_modules/expo-router/entry.bundle:66319:41 StaticContainer http://localhost:8081/node_modules/expo-router/entry.bundle:165743:17 EnsureSingleNavigator http://localhost:8081/node_modules/expo-router/entry.bundle:163244:34 SceneView http://localhost:8081/node_modules/expo-router/entry.bundle:165668:13 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 Background http://localhost:8081/node_modules/expo-router/entry.bundle:167963:43 Screen http://localhost:8081/node_modules/expo-router/entry.bundle:169069:73 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 ResourceSavingScene@http://localhost:8081/node_modules/expo-router/entry.bundle:168879:43 MaybeScreen http://localhost:8081/node_modules/expo-router/entry.bundle:194647:43 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 MaybeScreenContainer http://localhost:8081/node_modules/expo-router/entry.bundle:194632:43 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 AnimatedComponent http://localhost:8081/node_modules/expo-router/entry.bundle:137743:14 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 AnimatedComponent http://localhost:8081/node_modules/expo-router/entry.bundle:137743:14 Dummy http://localhost:8081/node_modules/expo-router/entry.bundle:194577:30 Drawer http://localhost:8081/node_modules/expo-router/entry.bundle:195353:13 DrawerViewBase http://localhost:8081/node_modules/expo-router/entry.bundle:193870:12 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 SafeAreaProviderCompat http://localhost:8081/node_modules/expo-router/entry.bundle:168965:25 DrawerView http://localhost:8081/node_modules/expo-router/entry.bundle:194056:43 PreventRemoveProvider http://localhost:8081/node_modules/expo-router/entry.bundle:164734:14 NavigationContent http://localhost:8081/node_modules/expo-router/entry.bundle:165449:18 http://localhost:8081/node_modules/expo-router/entry.bundle:165465:31 DrawerNavigator http://localhost:8081/node_modules/expo-router/entry.bundle:193742:57 http://localhost:8081/node_modules/expo-router/entry.bundle:65839:41 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 GestureHandlerRootView http://localhost:8081/node_modules/expo-router/entry.bundle:188967:43 Route http://localhost:8081/node_modules/expo-router/entry.bundle:65953:44 Route http://localhost:8081/node_modules/expo-router/entry.bundle:66319:41 StaticContainer http://localhost:8081/node_modules/expo-router/entry.bundle:165743:17 EnsureSingleNavigator http://localhost:8081/node_modules/expo-router/entry.bundle:163244:34 SceneView http://localhost:8081/node_modules/expo-router/entry.bundle:165668:13 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 Background http://localhost:8081/node_modules/expo-router/entry.bundle:167963:43 Screen http://localhost:8081/node_modules/expo-router/entry.bundle:169069:73 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 SafeAreaProviderCompat http://localhost:8081/node_modules/expo-router/entry.bundle:168965:25 NativeStackView http://localhost:8081/node_modules/expo-router/entry.bundle:167645:13 PreventRemoveProvider http://localhost:8081/node_modules/expo-router/entry.bundle:164734:14 NavigationContent http://localhost:8081/node_modules/expo-router/entry.bundle:165449:18 http://localhost:8081/node_modules/expo-router/entry.bundle:165465:31 NativeStackNavigator http://localhost:8081/node_modules/expo-router/entry.bundle:161280:43 http://localhost:8081/node_modules/expo-router/entry.bundle:65839:41 ThemeProvider http://localhost:8081/node_modules/expo-router/entry.bundle:166733:30 RootLayout http://localhost:8081/node_modules/expo-router/entry.bundle:159820:45 Route http://localhost:8081/node_modules/expo-router/entry.bundle:65953:44 Route http://localhost:8081/node_modules/expo-router/entry.bundle:66319:41 View http://localhost:8081/node_modules/expo-router/entry.bundle:39052:26 NativeSafeAreaProvider http://localhost:8081/node_modules/expo-router/entry.bundle:160992:19 SafeAreaProvider http://localhost:8081/node_modules/expo-router/entry.bundle:160865:45 wrapper http://localhost:8081/node_modules/expo-router/entry.bundle:73992:46 EnsureSingleNavigator http://localhost:8081/node_modules/expo-router/entry.bundle:163244:34 BaseNavigationContainer http://localhost:8081/node_modules/expo-router/entry.bundle:161911:13 ThemeProvider http://localhost:8081/node_modules/expo-router/entry.bundle:166733:30 NavigationContainerInner http://localhost:8081/node_modules/expo-router/entry.bundle:74246:25 ContextNavigator http://localhost:8081/node_modules/expo-router/entry.bundle:74026:43 ExpoRoot http://localhost:8081/node_modules/expo-router/entry.bundle:73981:41 _HelmetProvider http://localhost:8081/node_modules/expo-router/entry.bundle:201567:12 AppContainer http://localhost:8081/node_modules/expo-router/entry.bundle:51978:24 Showing all frames

NewCoder2023 avatar Jul 29 '24 11:07 NewCoder2023

Same here, any workaround ?

oussamabenkortbi avatar Oct 20 '24 21:10 oussamabenkortbi

I managed it by using a global state management (Zustand) and listen to changes and change color accordingly but it’s not that satisfying.

NewCoder2023 avatar Oct 24 '24 22:10 NewCoder2023

Not working too with storybook web

tiavina-mika avatar Jan 21 '25 07:01 tiavina-mika

if (Platform.OS === 'web') {
  Appearance.setColorScheme = (scheme) => {
    document.documentElement.setAttribute('data-theme', scheme);
  };

  Appearance.getColorScheme = () => {
    const systemValue = window.matchMedia('(prefers-color-scheme: dark)').matches
      ? 'dark'
      : 'light';
    const userValue = document.documentElement.getAttribute('data-theme');
    return userValue && userValue !== 'null' ? userValue : systemValue;
  };

  Appearance.addChangeListener = (listener) => {
    // Listen for changes of system value
    const systemValueListener = (e) => {
      const newSystemValue = e.matches ? 'dark' : 'light';
      const userValue = document.documentElement.getAttribute('data-theme');
      listener({
        colorScheme: userValue && userValue !== 'null' ? userValue : newSystemValue,
      });
    };
    const systemValue = window.matchMedia('(prefers-color-scheme: dark)');
    systemValue.addEventListener('change', systemValueListener);

    // Listen for changes of user set value
    const observer = new MutationObserver((mutationsList) => {
      for (const mutation of mutationsList) {
        if (mutation.attributeName === 'data-theme') {
          listener({ colorScheme: Appearance.getColorScheme() });
        }
      }
    });
    observer.observe(document.documentElement, { attributes: true });

    function remove() {
      systemValue.removeEventListener('change', systemValueListener);
      observer.disconnect();
    }

    return { remove };
  };
}

Was able to use this as a workaround, behavior is equivalent to iOS and android. Uses the system dark/light settings until setColorScheme is called, in which it will always use the user-defined value.

YoussefHenna avatar Jan 28 '25 13:01 YoussefHenna

I'm using this in the meantime. Seems to work fine.

import { useColorScheme } from 'react-native'
import { create } from 'zustand'

const useStore = create<{
  isDarkMode: boolean | undefined
  setIsDarkMode: (isDarkMode: boolean | undefined) => void
}>(set => ({
  isDarkMode: undefined,
  setIsDarkMode: isDarkMode => set({ isDarkMode }),
}))

const useOverrideValue = () => useStore(s => s.isDarkMode)
const useSetOverrideValue = () => useStore(s => s.setIsDarkMode)

export const useIsDarkMode = () => {
  const fallback = useColorScheme()
  const override = useOverrideValue()
  const setOverride = useSetOverrideValue()
  return [override ?? fallback === 'dark', setOverride] as const
}

jmadelaine avatar Feb 01 '25 13:02 jmadelaine

Any idea why this is still an open issue? The function itself is just missing in total, while the docs state differently. Seems like a missing implementation. Should the docs show that it is not supported for web in the meantime?

BenGroot avatar Jun 09 '25 15:06 BenGroot

I'm having the same issue. Very perplexed that this issue has been open for a year. The docs indicate that Appearance.setColorScheme should work.

May I request fixing this bug or updating the docs to indicate that setColorScheme does not appear on the Appearance object in web?

https://reactnative.dev/docs/appearance?guide=web

SterlingVix avatar Jul 28 '25 19:07 SterlingVix

I'm using @YoussefHenna code, with a little modification:

// On my machine `matchMedia` returns a `MediaQueryList`, so `systemValue` is always `dark`.
const systemValue = window.matchMedia('(prefers-color-scheme: dark)') ? 'dark': 'light';
// fixed by using
const systemValue = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark': 'light';

OoDeLally avatar Aug 01 '25 21:08 OoDeLally