From 927be3683b1ac9e855af5e13bc805764ed377b2d Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 14 Nov 2025 18:52:05 -0300 Subject: [PATCH 1/8] fix: improved theme logic --- .../__tests__/hooks/useAppKitTheme.test.tsx | 22 +++++----- packages/appkit/src/hooks/useAppKitTheme.ts | 3 +- packages/appkit/src/modal/w3m-modal/index.tsx | 8 ++-- .../core/src/controllers/ThemeController.ts | 43 +++++++++++-------- 4 files changed, 42 insertions(+), 34 deletions(-) diff --git a/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx b/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx index e2340c65..c298a721 100644 --- a/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx +++ b/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx @@ -21,7 +21,7 @@ jest.mock('valtio', () => ({ jest.mock('@reown/appkit-core-react-native', () => ({ ThemeController: { state: { - themeMode: undefined, + themeMode: 'light', themeVariables: {} }, setThemeMode: jest.fn(), @@ -42,7 +42,7 @@ describe('useAppKitTheme', () => { jest.clearAllMocks(); // Reset ThemeController state ThemeController.state = { - themeMode: undefined, + themeMode: 'light', themeVariables: {} }; }); @@ -61,7 +61,7 @@ describe('useAppKitTheme', () => { it('should return initial theme state', () => { const { result } = renderHook(() => useAppKitTheme(), { wrapper }); - expect(result.current.themeMode).toBeUndefined(); + expect(result.current.themeMode).toBe('light'); expect(result.current.themeVariables).toStrictEqual({}); }); @@ -99,24 +99,24 @@ describe('useAppKitTheme', () => { expect(result.current.themeVariables).toEqual(themeVariables); }); - it('should call ThemeController.setThemeMode when setThemeMode is called', () => { + it('should call ThemeController.setDefaultThemeMode when setThemeMode is called', () => { const { result } = renderHook(() => useAppKitTheme(), { wrapper }); act(() => { result.current.setThemeMode('dark'); }); - expect(ThemeController.setThemeMode).toHaveBeenCalledWith('dark'); + expect(ThemeController.setDefaultThemeMode).toHaveBeenCalledWith('dark'); }); - it('should call ThemeController.setThemeMode with undefined', () => { + it('should call ThemeController.setDefaultThemeMode with undefined', () => { const { result } = renderHook(() => useAppKitTheme(), { wrapper }); act(() => { result.current.setThemeMode(undefined); }); - expect(ThemeController.setThemeMode).toHaveBeenCalledWith(undefined); + expect(ThemeController.setDefaultThemeMode).toHaveBeenCalledWith(undefined); }); it('should call ThemeController.setThemeVariables when setThemeVariables is called', () => { @@ -172,10 +172,10 @@ describe('useAppKitTheme', () => { result.current.setThemeMode(undefined); }); - expect(ThemeController.setThemeMode).toHaveBeenCalledTimes(3); - expect(ThemeController.setThemeMode).toHaveBeenNthCalledWith(1, 'dark'); - expect(ThemeController.setThemeMode).toHaveBeenNthCalledWith(2, 'light'); - expect(ThemeController.setThemeMode).toHaveBeenNthCalledWith(3, undefined); + expect(ThemeController.setDefaultThemeMode).toHaveBeenCalledTimes(3); + expect(ThemeController.setDefaultThemeMode).toHaveBeenNthCalledWith(1, 'dark'); + expect(ThemeController.setDefaultThemeMode).toHaveBeenNthCalledWith(2, 'light'); + expect(ThemeController.setDefaultThemeMode).toHaveBeenNthCalledWith(3, undefined); }); it('should handle multiple setThemeVariables calls', () => { diff --git a/packages/appkit/src/hooks/useAppKitTheme.ts b/packages/appkit/src/hooks/useAppKitTheme.ts index 9e5b71a1..edaa4fb0 100644 --- a/packages/appkit/src/hooks/useAppKitTheme.ts +++ b/packages/appkit/src/hooks/useAppKitTheme.ts @@ -63,11 +63,12 @@ export interface UseAppKitThemeReturn { */ export function useAppKitTheme(): UseAppKitThemeReturn { useAppKitContext(); + const { themeMode, themeVariables } = useSnapshot(ThemeController.state); const stableFunctions = useMemo( () => ({ - setThemeMode: ThemeController.setThemeMode.bind(ThemeController), + setThemeMode: ThemeController.setDefaultThemeMode.bind(ThemeController), setThemeVariables: ThemeController.setThemeVariables.bind(ThemeController) }), [] diff --git a/packages/appkit/src/modal/w3m-modal/index.tsx b/packages/appkit/src/modal/w3m-modal/index.tsx index 2ffd2a5e..530dccef 100644 --- a/packages/appkit/src/modal/w3m-modal/index.tsx +++ b/packages/appkit/src/modal/w3m-modal/index.tsx @@ -23,7 +23,7 @@ export function AppKit() { const { bottom, top } = useSafeAreaInsets(); const { close } = useInternalAppKit(); const { open } = useSnapshot(ModalController.state); - const { themeMode, themeVariables, defaultThemeMode } = useSnapshot(ThemeController.state); + const { themeMode, themeVariables } = useSnapshot(ThemeController.state); const { projectId } = useSnapshot(OptionsController.state); const handleBackPress = () => { @@ -35,10 +35,8 @@ export function AppKit() { }; useEffect(() => { - if (theme && !defaultThemeMode) { - ThemeController.setThemeMode(theme); - } - }, [theme, defaultThemeMode]); + ThemeController.setSystemThemeMode(theme); + }, [theme]); const prefetch = useCallback(async () => { await ApiController.prefetch(); diff --git a/packages/core/src/controllers/ThemeController.ts b/packages/core/src/controllers/ThemeController.ts index c64a5507..a0fad715 100644 --- a/packages/core/src/controllers/ThemeController.ts +++ b/packages/core/src/controllers/ThemeController.ts @@ -1,49 +1,58 @@ -import { Appearance } from 'react-native'; import { proxy, subscribe as sub } from 'valtio'; import type { ThemeMode, ThemeVariables } from '@reown/appkit-common-react-native'; +import { derive } from 'derive-valtio'; // -- Types --------------------------------------------- // export interface ThemeControllerState { - themeMode?: ThemeMode; - defaultThemeMode?: ThemeMode; + systemThemeMode?: ThemeMode | null; + defaultThemeMode?: ThemeMode | null; themeVariables: ThemeVariables; } // -- State --------------------------------------------- // -const state = proxy({ - themeMode: undefined, +const baseState = proxy({ + systemThemeMode: undefined, defaultThemeMode: undefined, themeVariables: {} }); +// -- Derived State ------------------------------------- // +const derivedState = derive( + { + themeMode: (get): ThemeMode => { + const snap = get(baseState); + + return snap.defaultThemeMode ?? snap.systemThemeMode ?? 'light'; + } + }, + { + proxy: baseState + } +); + // -- Controller ---------------------------------------- // export const ThemeController = { - state, + state: derivedState, subscribe(callback: (newState: ThemeControllerState) => void) { - return sub(state, () => callback(state)); + return sub(derivedState, () => callback(derivedState)); }, - setThemeMode(themeMode?: ThemeControllerState['themeMode']) { - if (!themeMode) { - state.themeMode = (Appearance.getColorScheme() ?? 'light') as ThemeMode; - } else { - state.themeMode = themeMode; - } + setSystemThemeMode(systemThemeMode?: ThemeControllerState['systemThemeMode']) { + baseState.systemThemeMode = systemThemeMode ?? 'light'; }, setDefaultThemeMode(themeMode?: ThemeControllerState['defaultThemeMode']) { - state.defaultThemeMode = themeMode; - this.setThemeMode(themeMode); + baseState.defaultThemeMode = themeMode; }, setThemeVariables(themeVariables?: ThemeControllerState['themeVariables']) { if (!themeVariables) { - state.themeVariables = {}; + baseState.themeVariables = {}; return; } - state.themeVariables = { ...state.themeVariables, ...themeVariables }; + baseState.themeVariables = { ...baseState.themeVariables, ...themeVariables }; } }; From b599e77e7ab80cc4ee181d6dab623f8554ccfcbf Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 14 Nov 2025 19:01:30 -0300 Subject: [PATCH 2/8] chore: code improvements --- packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx | 2 +- packages/appkit/src/hooks/useAppKitTheme.ts | 4 ++-- packages/appkit/src/modal/w3m-modal/index.tsx | 2 +- packages/core/src/controllers/ThemeController.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx b/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx index c298a721..cfc5ea8d 100644 --- a/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx +++ b/packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx @@ -24,7 +24,7 @@ jest.mock('@reown/appkit-core-react-native', () => ({ themeMode: 'light', themeVariables: {} }, - setThemeMode: jest.fn(), + setDefaultThemeMode: jest.fn(), setThemeVariables: jest.fn() } })); diff --git a/packages/appkit/src/hooks/useAppKitTheme.ts b/packages/appkit/src/hooks/useAppKitTheme.ts index edaa4fb0..99e4e8fa 100644 --- a/packages/appkit/src/hooks/useAppKitTheme.ts +++ b/packages/appkit/src/hooks/useAppKitTheme.ts @@ -8,8 +8,8 @@ import { useAppKitContext } from './useAppKitContext'; * Interface representing the result of the useAppKitTheme hook */ export interface UseAppKitThemeReturn { - /** The current theme mode ('dark' or 'light'), or undefined if using system default */ - themeMode?: ThemeMode; + /** The current theme mode ('dark' or 'light') */ + themeMode: ThemeMode; /** The current theme variables, currently only supports 'accent' color */ themeVariables: ThemeVariables; /** Function to set the theme mode */ diff --git a/packages/appkit/src/modal/w3m-modal/index.tsx b/packages/appkit/src/modal/w3m-modal/index.tsx index 530dccef..4ea75fa0 100644 --- a/packages/appkit/src/modal/w3m-modal/index.tsx +++ b/packages/appkit/src/modal/w3m-modal/index.tsx @@ -35,7 +35,7 @@ export function AppKit() { }; useEffect(() => { - ThemeController.setSystemThemeMode(theme); + ThemeController.setSystemThemeMode(theme ?? undefined); }, [theme]); const prefetch = useCallback(async () => { diff --git a/packages/core/src/controllers/ThemeController.ts b/packages/core/src/controllers/ThemeController.ts index a0fad715..ea9c3321 100644 --- a/packages/core/src/controllers/ThemeController.ts +++ b/packages/core/src/controllers/ThemeController.ts @@ -4,8 +4,8 @@ import { derive } from 'derive-valtio'; // -- Types --------------------------------------------- // export interface ThemeControllerState { - systemThemeMode?: ThemeMode | null; - defaultThemeMode?: ThemeMode | null; + systemThemeMode?: ThemeMode; + defaultThemeMode?: ThemeMode; themeVariables: ThemeVariables; } From a7f1584ca72928d3d9cf07e29f8033b1f656e223 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 14 Nov 2025 19:28:37 -0300 Subject: [PATCH 3/8] chore: fixed tests --- .../controllers/ThemeController.test.ts | 136 ++++++++++-------- 1 file changed, 77 insertions(+), 59 deletions(-) diff --git a/packages/core/src/__tests__/controllers/ThemeController.test.ts b/packages/core/src/__tests__/controllers/ThemeController.test.ts index c2d3e06d..f98ab1d0 100644 --- a/packages/core/src/__tests__/controllers/ThemeController.test.ts +++ b/packages/core/src/__tests__/controllers/ThemeController.test.ts @@ -1,83 +1,95 @@ -import { Appearance } from 'react-native'; import { ThemeController } from '../../index'; -// Mock react-native Appearance -jest.mock('react-native', () => ({ - Appearance: { - getColorScheme: jest.fn() - } -})); - -const mockedAppearance = Appearance as jest.Mocked; - // -- Tests -------------------------------------------------------------------- describe('ThemeController', () => { beforeEach(() => { - // Reset state before each test - ThemeController.setThemeMode(); - ThemeController.setDefaultThemeMode(); - ThemeController.setThemeVariables(); + // Reset state + ThemeController.setDefaultThemeMode(undefined); + ThemeController.setSystemThemeMode(); + ThemeController.setThemeVariables(undefined); jest.clearAllMocks(); }); describe('initial state', () => { it('should have valid default state', () => { - expect(ThemeController.state.themeMode).toBeDefined(); - expect(ThemeController.state.defaultThemeMode).toBeUndefined(); - expect(ThemeController.state.themeVariables).toEqual({}); + const state = ThemeController.state; + expect(state.themeMode).toBeDefined(); + expect(state.defaultThemeMode).toBeUndefined(); + expect(state.themeVariables).toEqual({}); }); }); - describe('setThemeMode', () => { - it('should set theme mode to light', () => { - ThemeController.setThemeMode('light'); - expect(ThemeController.state.themeMode).toBe('light'); + describe('setDefaultThemeMode', () => { + it('should set default theme mode to light', () => { + ThemeController.setDefaultThemeMode('light'); + const state = ThemeController.state; + expect(state.defaultThemeMode).toBe('light'); }); - it('should set theme mode to dark', () => { - ThemeController.setThemeMode('dark'); - expect(ThemeController.state.themeMode).toBe('dark'); + it('should set default theme mode to dark', () => { + ThemeController.setDefaultThemeMode('dark'); + const state = ThemeController.state; + expect(state.defaultThemeMode).toBe('dark'); }); - it('should fall back to system theme when undefined and system is dark', () => { - mockedAppearance.getColorScheme.mockReturnValue('dark'); - ThemeController.setThemeMode(); - expect(ThemeController.state.themeMode).toBe('dark'); - expect(mockedAppearance.getColorScheme).toHaveBeenCalled(); + it('should clear default theme mode when set to undefined', () => { + ThemeController.setDefaultThemeMode('dark'); + ThemeController.setDefaultThemeMode(undefined); + const state = ThemeController.state; + expect(state.defaultThemeMode).toBeUndefined(); }); - it('should fall back to system theme when undefined and system is light', () => { - mockedAppearance.getColorScheme.mockReturnValue('light'); - ThemeController.setThemeMode(); - expect(ThemeController.state.themeMode).toBe('light'); - expect(mockedAppearance.getColorScheme).toHaveBeenCalled(); + it('should update default theme mode from light to dark', () => { + ThemeController.setDefaultThemeMode('light'); + ThemeController.setDefaultThemeMode('dark'); + const state = ThemeController.state; + expect(state.defaultThemeMode).toBe('dark'); }); + }); - it('should default to light when system returns null', () => { - mockedAppearance.getColorScheme.mockReturnValue(null); - ThemeController.setThemeMode(); - expect(ThemeController.state.themeMode).toBe('light'); + describe('setSystemThemeMode', () => { + it('should set system theme mode to dark', () => { + ThemeController.setSystemThemeMode('dark'); + const state = ThemeController.state; + expect(state.systemThemeMode).toBe('dark'); + }); + + it('should set system theme mode to light', () => { + ThemeController.setSystemThemeMode('light'); + const state = ThemeController.state; + expect(state.systemThemeMode).toBe('light'); + }); + + it('should default to light when called without arguments', () => { + ThemeController.setSystemThemeMode(); + const state = ThemeController.state; + expect(state.systemThemeMode).toBe('light'); + }); + + it('should update system theme mode from light to dark', () => { + ThemeController.setSystemThemeMode('light'); + ThemeController.setSystemThemeMode('dark'); + const state = ThemeController.state; + expect(state.systemThemeMode).toBe('dark'); }); }); - describe('setDefaultThemeMode', () => { - it('should set default theme mode and apply it', () => { + describe('theme mode precedence', () => { + it('should have both system and default theme modes set independently', () => { + ThemeController.setSystemThemeMode('light'); ThemeController.setDefaultThemeMode('dark'); - expect(ThemeController.state.defaultThemeMode).toBe('dark'); - expect(ThemeController.state.themeMode).toBe('dark'); + const state = ThemeController.state; + expect(state.systemThemeMode).toBe('light'); + expect(state.defaultThemeMode).toBe('dark'); }); - it('should set default theme mode to light and apply it', () => { + it('should allow default theme mode to be cleared while system remains', () => { + ThemeController.setSystemThemeMode('dark'); ThemeController.setDefaultThemeMode('light'); - expect(ThemeController.state.defaultThemeMode).toBe('light'); - expect(ThemeController.state.themeMode).toBe('light'); - }); - - it('should set default theme mode to undefined and fall back to system', () => { - mockedAppearance.getColorScheme.mockReturnValue('dark'); - ThemeController.setDefaultThemeMode(); - expect(ThemeController.state.defaultThemeMode).toBeUndefined(); - expect(ThemeController.state.themeMode).toBe('dark'); + ThemeController.setDefaultThemeMode(undefined); + const state = ThemeController.state; + expect(state.systemThemeMode).toBe('dark'); + expect(state.defaultThemeMode).toBeUndefined(); }); }); @@ -85,7 +97,8 @@ describe('ThemeController', () => { it('should set theme variables', () => { const variables = { accent: '#000000' }; ThemeController.setThemeVariables(variables); - expect(ThemeController.state.themeVariables).toEqual(variables); + const state = ThemeController.state; + expect(state.themeVariables).toEqual(variables); }); it('should override existing theme variables', () => { @@ -94,8 +107,9 @@ describe('ThemeController', () => { ThemeController.setThemeVariables(initialVariables); ThemeController.setThemeVariables(newVariables); + const state = ThemeController.state; - expect(ThemeController.state.themeVariables).toEqual({ + expect(state.themeVariables).toEqual({ accent: '#FFFFFF' }); }); @@ -103,15 +117,18 @@ describe('ThemeController', () => { it('should reset theme variables when undefined', () => { const variables = { accent: '#000000' }; ThemeController.setThemeVariables(variables); - expect(ThemeController.state.themeVariables).toEqual(variables); + let state = ThemeController.state; + expect(state.themeVariables).toEqual(variables); ThemeController.setThemeVariables(undefined); - expect(ThemeController.state.themeVariables).toEqual({}); + state = ThemeController.state; + expect(state.themeVariables).toEqual({}); }); it('should handle empty object', () => { ThemeController.setThemeVariables({}); - expect(ThemeController.state.themeVariables).toEqual({}); + const state = ThemeController.state; + expect(state.themeVariables).toEqual({}); }); }); @@ -134,9 +151,10 @@ describe('ThemeController', () => { describe('state immutability', () => { it('should maintain state reference but update values', () => { const stateRef = ThemeController.state; - ThemeController.setThemeMode('dark'); + ThemeController.setDefaultThemeMode('dark'); expect(ThemeController.state).toBe(stateRef); - expect(ThemeController.state.themeMode).toBe('dark'); + const state = ThemeController.state; + expect(state.defaultThemeMode).toBe('dark'); }); }); }); From 43a068e14b84f73c70877268d2dac91a5060c094 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:04:44 -0300 Subject: [PATCH 4/8] chore: code improvements --- .changeset/late-queens-talk.md | 13 +++++++++++++ packages/appkit/src/hooks/useAppKitTheme.ts | 2 +- .../controllers/ThemeController.test.ts | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .changeset/late-queens-talk.md diff --git a/.changeset/late-queens-talk.md b/.changeset/late-queens-talk.md new file mode 100644 index 00000000..f92de52b --- /dev/null +++ b/.changeset/late-queens-talk.md @@ -0,0 +1,13 @@ +--- +'@reown/appkit-react-native': patch +'@reown/appkit-bitcoin-react-native': patch +'@reown/appkit-coinbase-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-solana-react-native': patch +'@reown/appkit-ui-react-native': patch +'@reown/appkit-wagmi-react-native': patch +--- + +fix: refactors the theme management logic to introduce a clearer separation between system theme and user-defined default theme diff --git a/packages/appkit/src/hooks/useAppKitTheme.ts b/packages/appkit/src/hooks/useAppKitTheme.ts index 99e4e8fa..82579ee0 100644 --- a/packages/appkit/src/hooks/useAppKitTheme.ts +++ b/packages/appkit/src/hooks/useAppKitTheme.ts @@ -9,7 +9,7 @@ import { useAppKitContext } from './useAppKitContext'; */ export interface UseAppKitThemeReturn { /** The current theme mode ('dark' or 'light') */ - themeMode: ThemeMode; + themeMode?: ThemeMode; /** The current theme variables, currently only supports 'accent' color */ themeVariables: ThemeVariables; /** Function to set the theme mode */ diff --git a/packages/core/src/__tests__/controllers/ThemeController.test.ts b/packages/core/src/__tests__/controllers/ThemeController.test.ts index f98ab1d0..16551dc3 100644 --- a/packages/core/src/__tests__/controllers/ThemeController.test.ts +++ b/packages/core/src/__tests__/controllers/ThemeController.test.ts @@ -91,6 +91,24 @@ describe('ThemeController', () => { expect(state.systemThemeMode).toBe('dark'); expect(state.defaultThemeMode).toBeUndefined(); }); + it('should derive themeMode with correct priority: defaultThemeMode > systemThemeMode > light', () => { + // Initially, with no values set, should default to 'light' + ThemeController.setDefaultThemeMode(undefined); + ThemeController.setSystemThemeMode(); + expect(ThemeController.state.themeMode).toBe('light'); + + // When only systemThemeMode is set, themeMode should follow it + ThemeController.setSystemThemeMode('dark'); + expect(ThemeController.state.themeMode).toBe('dark'); + + // When defaultThemeMode is set, it takes priority over systemThemeMode + ThemeController.setDefaultThemeMode('light'); + expect(ThemeController.state.themeMode).toBe('light'); + + // When defaultThemeMode is cleared, falls back to systemThemeMode + ThemeController.setDefaultThemeMode(undefined); + expect(ThemeController.state.themeMode).toBe('dark'); + }); }); describe('setThemeVariables', () => { From 130047806a3bdcbddd0cd1bc56db745594181c13 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:09:50 -0300 Subject: [PATCH 5/8] Save work before archive --- packages/appkit/src/hooks/useAppKitTheme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/appkit/src/hooks/useAppKitTheme.ts b/packages/appkit/src/hooks/useAppKitTheme.ts index 82579ee0..99e4e8fa 100644 --- a/packages/appkit/src/hooks/useAppKitTheme.ts +++ b/packages/appkit/src/hooks/useAppKitTheme.ts @@ -9,7 +9,7 @@ import { useAppKitContext } from './useAppKitContext'; */ export interface UseAppKitThemeReturn { /** The current theme mode ('dark' or 'light') */ - themeMode?: ThemeMode; + themeMode: ThemeMode; /** The current theme variables, currently only supports 'accent' color */ themeVariables: ThemeVariables; /** Function to set the theme mode */ From f407f356a197d3e341a622d8e7c4651f0415676a Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:17:14 -0300 Subject: [PATCH 6/8] refactor: remove redundant useColorScheme from ThemeContext Since ThemeController now always provides a defined themeMode via derived state, the useColorScheme fallback in useTheme() is no longer needed. The theme priority is now handled entirely by ThemeController. Co-Authored-By: Claude Opus 4.5 --- packages/ui/src/context/ThemeContext.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/context/ThemeContext.tsx b/packages/ui/src/context/ThemeContext.tsx index 5282c0bd..69154e46 100644 --- a/packages/ui/src/context/ThemeContext.tsx +++ b/packages/ui/src/context/ThemeContext.tsx @@ -1,11 +1,10 @@ -import { useColorScheme } from 'react-native'; import { createContext, useContext, useMemo, type ReactNode } from 'react'; import type { ThemeMode, ThemeVariables } from '@reown/appkit-common-react-native'; import { DarkTheme, LightTheme, getAccentColors } from '../utils/ThemeUtil'; type ThemeContextType = { - themeMode?: ThemeMode; + themeMode: ThemeMode; themeVariables?: ThemeVariables; }; @@ -13,7 +12,7 @@ export const ThemeContext = createContext(undefine interface ThemeProviderProps { children: ReactNode; - themeMode?: ThemeMode; + themeMode: ThemeMode; themeVariables?: ThemeVariables; } @@ -25,11 +24,9 @@ export function ThemeProvider({ children, themeMode, themeVariables }: ThemeProv export function useTheme() { const context = useContext(ThemeContext); - const scheme = useColorScheme(); return useMemo(() => { - // If the theme mode is not set, use the system color scheme - const themeMode = context?.themeMode ?? scheme; + const themeMode = context?.themeMode ?? 'light'; const themeVariables = context?.themeVariables ?? {}; let Theme = themeMode === 'dark' ? DarkTheme : LightTheme; @@ -42,5 +39,5 @@ export function useTheme() { } return Theme; - }, [context?.themeMode, context?.themeVariables, scheme]); + }, [context?.themeMode, context?.themeVariables]); } From 389a336fca3e2c5acac0a5020e3a2f295fc62ee2 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:33:02 -0300 Subject: [PATCH 7/8] chore: solved prettier issue --- .../core/src/__tests__/controllers/ThemeController.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/__tests__/controllers/ThemeController.test.ts b/packages/core/src/__tests__/controllers/ThemeController.test.ts index 16551dc3..c7f35fa4 100644 --- a/packages/core/src/__tests__/controllers/ThemeController.test.ts +++ b/packages/core/src/__tests__/controllers/ThemeController.test.ts @@ -96,15 +96,15 @@ describe('ThemeController', () => { ThemeController.setDefaultThemeMode(undefined); ThemeController.setSystemThemeMode(); expect(ThemeController.state.themeMode).toBe('light'); - + // When only systemThemeMode is set, themeMode should follow it ThemeController.setSystemThemeMode('dark'); expect(ThemeController.state.themeMode).toBe('dark'); - + // When defaultThemeMode is set, it takes priority over systemThemeMode ThemeController.setDefaultThemeMode('light'); expect(ThemeController.state.themeMode).toBe('light'); - + // When defaultThemeMode is cleared, falls back to systemThemeMode ThemeController.setDefaultThemeMode(undefined); expect(ThemeController.state.themeMode).toBe('dark'); From 5714a288560caa7df709c691b780a4478204fa6c Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:57:15 -0300 Subject: [PATCH 8/8] refactor: replace derive-valtio with native getter for themeMode Use a native JavaScript getter instead of derive-valtio for computing themeMode. This ensures synchronous updates and removes the external dependency. The getter pattern is the recommended valtio approach for computed properties that reference sibling properties. Co-Authored-By: Claude Opus 4.5 --- .../core/src/controllers/ThemeController.ts | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/packages/core/src/controllers/ThemeController.ts b/packages/core/src/controllers/ThemeController.ts index ea9c3321..9d8d2d2c 100644 --- a/packages/core/src/controllers/ThemeController.ts +++ b/packages/core/src/controllers/ThemeController.ts @@ -1,58 +1,48 @@ import { proxy, subscribe as sub } from 'valtio'; import type { ThemeMode, ThemeVariables } from '@reown/appkit-common-react-native'; -import { derive } from 'derive-valtio'; // -- Types --------------------------------------------- // export interface ThemeControllerState { + themeMode: ThemeMode; systemThemeMode?: ThemeMode; defaultThemeMode?: ThemeMode; themeVariables: ThemeVariables; } // -- State --------------------------------------------- // -const baseState = proxy({ - systemThemeMode: undefined, - defaultThemeMode: undefined, - themeVariables: {} -}); - -// -- Derived State ------------------------------------- // -const derivedState = derive( - { - themeMode: (get): ThemeMode => { - const snap = get(baseState); - - return snap.defaultThemeMode ?? snap.systemThemeMode ?? 'light'; - } - }, - { - proxy: baseState +const state = proxy({ + systemThemeMode: undefined as ThemeMode | undefined, + defaultThemeMode: undefined as ThemeMode | undefined, + themeVariables: {} as ThemeVariables, + get themeMode(): ThemeMode { + // eslint-disable-next-line valtio/avoid-this-in-proxy -- using `this` for sibling property access in getters is the recommended valtio pattern for computed properties + return this.defaultThemeMode ?? this.systemThemeMode ?? 'light'; } -); +}); // -- Controller ---------------------------------------- // export const ThemeController = { - state: derivedState, + state: state as ThemeControllerState, subscribe(callback: (newState: ThemeControllerState) => void) { - return sub(derivedState, () => callback(derivedState)); + return sub(state, () => callback(state)); }, setSystemThemeMode(systemThemeMode?: ThemeControllerState['systemThemeMode']) { - baseState.systemThemeMode = systemThemeMode ?? 'light'; + state.systemThemeMode = systemThemeMode ?? 'light'; }, setDefaultThemeMode(themeMode?: ThemeControllerState['defaultThemeMode']) { - baseState.defaultThemeMode = themeMode; + state.defaultThemeMode = themeMode; }, setThemeVariables(themeVariables?: ThemeControllerState['themeVariables']) { if (!themeVariables) { - baseState.themeVariables = {}; + state.themeVariables = {}; return; } - baseState.themeVariables = { ...baseState.themeVariables, ...themeVariables }; + state.themeVariables = { ...state.themeVariables, ...themeVariables }; } };