Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@ignaciosantise
Copy link
Collaborator

@ignaciosantise ignaciosantise commented Nov 14, 2025

Summary

Improved theme and setTheme logic


Note

Refactors theme logic to derive themeMode from systemThemeMode and defaultThemeMode, replacing setThemeMode, and updates hook, modal, and tests accordingly.

  • Core (ThemeController):
    • Introduces baseState with systemThemeMode, defaultThemeMode, and themeVariables and derives themeMode via derive-valtio.
    • Replaces setThemeMode with setSystemThemeMode and setDefaultThemeMode; removes reliance on Appearance.
    • Updates subscribe to use derived state; adjusts state mutations to baseState.
    • Revamps tests to validate derived themeMode, precedence, and new setters.
  • AppKit:
    • useAppKitTheme: themeMode now required; uses ThemeController.setDefaultThemeMode and exposes stable setters.
    • Modal (w3m-modal): syncs system theme via ThemeController.setSystemThemeMode(theme ?? undefined); removes defaultThemeMode usage in snapshot.
    • Updates related tests to assert new API and default light behavior.

Written by Cursor Bugbot for commit a7f1584. This will update automatically on new commits. Configure here.

Copilot AI review requested due to automatic review settings November 14, 2025 21:52
@changeset-bot
Copy link

changeset-bot bot commented Nov 14, 2025

⚠️ No Changeset found

Latest commit: a7f1584

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@claude
Copy link

claude bot commented Nov 14, 2025

Claude finished @ignaciosantise's task —— View job


PR Review: Theme Logic Improvements

Summary

This PR improves the theme management architecture by introducing a derived state pattern using derive-valtio, separating system theme from user-defined theme, and simplifying the theme resolution logic.


Issues Found

1. Type Safety Issue in setSystemThemeMode - packages/core/src/controllers/ThemeController.ts:42

The setSystemThemeMode method has a subtle type safety issue:

setSystemThemeMode(systemThemeMode?: ThemeControllerState['systemThemeMode']) {
  baseState.systemThemeMode = systemThemeMode ?? 'light';
}

Problem: When systemThemeMode is explicitly null (not undefined), the nullish coalescing operator (??) won't trigger, and baseState.systemThemeMode will be set to null. However, the derived state's fallback logic expects systemThemeMode to be either ThemeMode or nullish:

return snap.defaultThemeMode ?? snap.systemThemeMode ?? 'light';

Impact: When systemThemeMode is null, this would incorrectly return null instead of falling back to 'light'.

Recommendation: Either:

  • Change the type to disallow null: systemThemeMode?: ThemeMode (preferred)
  • Or explicitly handle null in the setter: baseState.systemThemeMode = systemThemeMode || 'light'

2. Missing Mock Update in Tests - packages/appkit/src/__tests__/hooks/useAppKitTheme.test.tsx:28

The test mock doesn't include the setDefaultThemeMode method that's now being used:

jest.mock('@reown/appkit-core-react-native', () => ({
  ThemeController: {
    state: {
      themeMode: 'light',
      themeVariables: {}
    },
    setThemeMode: jest.fn(),  // ❌ Old method (unused)
    setThemeVariables: jest.fn()
  }
}));

Problem: The mock includes setThemeMode but should include setDefaultThemeMode to match the actual implementation.

Recommendation: Update the mock to:

jest.mock('@reown/appkit-core-react-native', () => ({
  ThemeController: {
    state: {
      themeMode: 'light',
      themeVariables: {}
    },
    setDefaultThemeMode: jest.fn(),  // ✅ Correct method
    setThemeVariables: jest.fn()
  }
}));

3. Documentation Comment Accuracy - packages/appkit/src/hooks/useAppKitTheme.ts:11

The JSDoc comment states:

/** The current theme mode ('dark' or 'light'), or undefined if using system default */
themeMode?: ThemeMode;

Problem: With the new derived state implementation, themeMode is never undefined. The derived state always returns a fallback value:

return snap.defaultThemeMode ?? snap.systemThemeMode ?? 'light';

Recommendation: Update the JSDoc to reflect reality:

/** The current theme mode ('dark' or 'light'). Defaults to 'light' if no theme is set. */
themeMode: ThemeMode;  // Remove the optional modifier

4. React Native Dependency Removal - packages/core/src/controllers/ThemeController.ts

Positive observation: The removal of the Appearance import from react-native in the core controller is good architectural practice. This moves the platform-specific logic to the UI layer (w3m-modal/index.tsx:38) where it belongs, making the core controller more portable and testable.

However, the w3m-modal/index.tsx:22 still imports useColorScheme:

const theme = useColorScheme();

Potential Issue: useColorScheme() can return null on initial render or when color scheme is unavailable. The current code passes this directly to setSystemThemeMode(theme), which would set it to null, triggering Issue #1 above.

Recommendation: Add null handling:

useEffect(() => {
  ThemeController.setSystemThemeMode(theme ?? undefined);
}, [theme]);

5. Missing Type Safety in Hook Return - packages/appkit/src/hooks/useAppKitTheme.ts:10-19

The return type interface declares themeMode as optional, but the derived state guarantees it's always defined:

export interface UseAppKitThemeReturn {
  themeMode?: ThemeMode;  // ❌ Shouldn't be optional
  // ...
}

Recommendation: Remove the optional modifier for consistency with the actual behavior:

export interface UseAppKitThemeReturn {
  themeMode: ThemeMode;  // ✅ Always defined
  // ...
}

Architecture & Best Practices

Good Patterns

  1. Separation of Concerns: The new architecture cleanly separates system theme (systemThemeMode) from user preference (defaultThemeMode)
  2. Derived State Pattern: Using derive-valtio for computed theme mode is an excellent use of Valtio's reactivity system
  3. Immutability: Properly spreading objects when merging theme variables
  4. Function Stability: Using useMemo with .bind() for stable function references in the hook is correct
  5. Test Coverage: Comprehensive test updates showing good testing discipline

📝 Suggestions

  1. The naming could be clearer: consider userThemeMode instead of defaultThemeMode to better indicate it's user-set vs. default fallback
  2. Consider adding JSDoc comments to the controller methods to document the theme resolution priority: user theme → system theme → 'light'

Security & Performance

  • ✅ No security concerns identified
  • ✅ Performance is good - derived state is efficient and only recomputes when dependencies change
  • ✅ No unnecessary re-renders introduced

Missing Changeset

Note: This PR needs a changeset. Use the changeset-bot's provided link or run npx changeset locally to add one for @reown/appkit-core-react-native and @reown/appkit-react-native with semver type patch.


Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the theme management logic to introduce a clearer separation between system theme and user-defined default theme using valtio's derived state pattern.

Key Changes:

  • Introduced derive-valtio to create a computed themeMode that prioritizes defaultThemeMode over systemThemeMode
  • Renamed setThemeMode to setDefaultThemeMode for the user-facing API and added setSystemThemeMode for tracking system theme changes
  • Updated the modal to always track system theme changes via setSystemThemeMode, while the hook API uses setDefaultThemeMode to respect user preferences

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
packages/core/src/controllers/ThemeController.ts Refactored to use derived state pattern with separate system and default theme modes
packages/appkit/src/modal/w3m-modal/index.tsx Updated to use setSystemThemeMode for tracking system theme changes
packages/appkit/src/hooks/useAppKitTheme.ts Changed setThemeMode to call setDefaultThemeMode internally
packages/appkit/src/tests/hooks/useAppKitTheme.test.tsx Updated test expectations to reflect new default behavior and API changes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 7 to 8
systemThemeMode?: ThemeMode | null;
defaultThemeMode?: ThemeMode | null;
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type inconsistency: systemThemeMode and defaultThemeMode are typed as ThemeMode | null but are initialized with undefined. This creates a mismatch between the type definition and the actual runtime values. Consider either:

  1. Changing the type to ThemeMode | null | undefined or ThemeMode | undefined
  2. Initializing with null instead of undefined on lines 14-15

This inconsistency could lead to type-safety issues when accessing these properties.

Copilot uses AI. Check for mistakes.
state.themeMode = themeMode;
}
setSystemThemeMode(systemThemeMode?: ThemeControllerState['systemThemeMode']) {
baseState.systemThemeMode = systemThemeMode ?? 'light';
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setSystemThemeMode function always defaults to 'light' when the parameter is undefined or null. However, when the system theme changes to null (which useColorScheme() can return), this will force it to 'light' rather than preserving the null value. This could prevent proper fallback to the default theme mode. Consider allowing null to be set when explicitly passed, or handle the React Native's useColorScheme() null return value more gracefully.

Suggested change
baseState.systemThemeMode = systemThemeMode ?? 'light';
baseState.systemThemeMode = systemThemeMode === undefined ? 'light' : systemThemeMode;

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants