From 1536786d2cfd07e356d828024a1c2c09331c9eec Mon Sep 17 00:00:00 2001 From: bartlomiej obudzinski Date: Thu, 16 Apr 2026 13:38:02 +0200 Subject: [PATCH] perf: defer styles computation from module import to provider render --- .../ThemeStylesContextProvider/default.ts | 39 ++++++++++++++----- src/stories/CheckboxWithLabel.stories.tsx | 6 ++- src/stories/Composer.stories.tsx | 7 ++-- src/stories/DragAndDrop.stories.tsx | 5 ++- src/stories/Form.stories.tsx | 5 ++- src/stories/NumberWithSymbolForm.stories.tsx | 6 ++- src/styles/index.ts | 4 -- src/styles/utils/index.ts | 6 --- 8 files changed, 49 insertions(+), 29 deletions(-) diff --git a/src/components/ThemeStylesContextProvider/default.ts b/src/components/ThemeStylesContextProvider/default.ts index 0a4f1d1a3404..b1b9b0a33292 100644 --- a/src/components/ThemeStylesContextProvider/default.ts +++ b/src/components/ThemeStylesContextProvider/default.ts @@ -1,15 +1,34 @@ -// eslint-disable-next-line no-restricted-imports -import {defaultStyles} from '@styles/index'; -// eslint-disable-next-line no-restricted-imports -import {DefaultStyleUtils} from '@styles/utils'; +import styles from '@src/styles'; +import {defaultTheme} from '@src/styles/theme'; +import createStyleUtils from '@src/styles/utils'; import type {ThemeStylesActionsContextType, ThemeStylesStateContextType} from './types'; -const defaultThemeStylesStateContextValue: ThemeStylesStateContextType = { - styles: defaultStyles, -}; +// Lazy defaults: defers the expensive styles(defaultTheme) call from module import +// time to first access. In production, ThemeStylesProvider supplies real values so +// these are never reached. Tests that render without the provider will trigger lazy +// initialization on first access. +let cachedState: ThemeStylesStateContextType | undefined; +let cachedActions: ThemeStylesActionsContextType | undefined; -const defaultThemeStylesActionsContextValue: ThemeStylesActionsContextType = { - StyleUtils: DefaultStyleUtils, -}; +const defaultThemeStylesStateContextValue = new Proxy({} as ThemeStylesStateContextType, { + get(_, prop: keyof ThemeStylesStateContextType) { + if (!cachedState) { + cachedState = {styles: styles(defaultTheme)}; + } + return cachedState[prop]; + }, +}); + +const defaultThemeStylesActionsContextValue = new Proxy({} as ThemeStylesActionsContextType, { + get(_, prop: keyof ThemeStylesActionsContextType) { + if (!cachedActions) { + if (!cachedState) { + cachedState = {styles: styles(defaultTheme)}; + } + cachedActions = {StyleUtils: createStyleUtils(defaultTheme, cachedState.styles)}; + } + return cachedActions[prop]; + }, +}); export {defaultThemeStylesStateContextValue, defaultThemeStylesActionsContextValue}; diff --git a/src/stories/CheckboxWithLabel.stories.tsx b/src/stories/CheckboxWithLabel.stories.tsx index 630a24100713..9fa2c3d30f77 100644 --- a/src/stories/CheckboxWithLabel.stories.tsx +++ b/src/stories/CheckboxWithLabel.stories.tsx @@ -3,8 +3,10 @@ import React from 'react'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; import type {CheckboxWithLabelProps} from '@components/CheckboxWithLabel'; import Text from '@components/Text'; -// eslint-disable-next-line no-restricted-imports -import {defaultStyles} from '@styles/index'; +import styles from '@src/styles'; +import {defaultTheme} from '@src/styles/theme'; + +const defaultStyles = styles(defaultTheme); type CheckboxWithLabelStory = StoryFn; diff --git a/src/stories/Composer.stories.tsx b/src/stories/Composer.stories.tsx index 601303cd91dc..fd0d384603b8 100644 --- a/src/stories/Composer.stories.tsx +++ b/src/stories/Composer.stories.tsx @@ -9,11 +9,12 @@ import RenderHTML from '@components/RenderHTML'; import Text from '@components/Text'; import withNavigationFallback from '@components/withNavigationFallback'; import useStyleUtils from '@hooks/useStyleUtils'; -// eslint-disable-next-line no-restricted-imports -import {defaultTheme} from '@styles/theme'; -import {defaultStyles} from '@src/styles'; +import styles from '@src/styles'; +import {defaultTheme} from '@src/styles/theme'; import type {FileObject} from '@src/types/utils/Attachment'; +const defaultStyles = styles(defaultTheme); + const ComposerWithNavigation = withNavigationFallback(Composer); /** diff --git a/src/stories/DragAndDrop.stories.tsx b/src/stories/DragAndDrop.stories.tsx index 6d77c10b06e6..e877b096639b 100644 --- a/src/stories/DragAndDrop.stories.tsx +++ b/src/stories/DragAndDrop.stories.tsx @@ -4,7 +4,10 @@ import {Image, View} from 'react-native'; import DragAndDropConsumer from '@components/DragAndDrop/Consumer'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; import Text from '@components/Text'; -import {defaultStyles} from '@src/styles'; +import styles from '@src/styles'; +import {defaultTheme} from '@src/styles/theme'; + +const defaultStyles = styles(defaultTheme); /** * We use the Component Story Format for writing stories. Follow the docs here: diff --git a/src/stories/Form.stories.tsx b/src/stories/Form.stories.tsx index 2a2b3e45f73c..82650136aae5 100644 --- a/src/stories/Form.stories.tsx +++ b/src/stories/Form.stories.tsx @@ -18,10 +18,13 @@ import {isRequiredFulfilled} from '@libs/ValidationUtils'; import {clearErrors, setDraftValues, setErrors, setIsLoading} from '@userActions/FormActions'; import CONST from '@src/CONST'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; -import {defaultStyles} from '@src/styles'; +import styles from '@src/styles'; +import {defaultTheme} from '@src/styles/theme'; import type {Form} from '@src/types/form'; import type {Network} from '@src/types/onyx'; +const defaultStyles = styles(defaultTheme); + type FormStory = StoryFn; type StorybookFormValues = { diff --git a/src/stories/NumberWithSymbolForm.stories.tsx b/src/stories/NumberWithSymbolForm.stories.tsx index 7b0ae8f46ca8..9246f9946d67 100644 --- a/src/stories/NumberWithSymbolForm.stories.tsx +++ b/src/stories/NumberWithSymbolForm.stories.tsx @@ -5,9 +5,11 @@ import NumberWithSymbolForm from '@components/NumberWithSymbolForm'; import type {NumberWithSymbolFormProps} from '@components/NumberWithSymbolForm'; import ScrollView from '@components/ScrollView'; import withNavigationFallback from '@components/withNavigationFallback'; -// eslint-disable-next-line no-restricted-imports -import {defaultStyles} from '@styles/index'; import CONST from '@src/CONST'; +import styles from '@src/styles'; +import {defaultTheme} from '@src/styles/theme'; + +const defaultStyles = styles(defaultTheme); type NumberWithSymbolFormStory = StoryFn; diff --git a/src/styles/index.ts b/src/styles/index.ts index 469cb79b3146..7827a831cfcc 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -19,7 +19,6 @@ import {getBrowser, isMobile, isMobileSafari, isSafari} from '@libs/Browser'; import getPlatform from '@libs/getPlatform'; import CONST from '@src/CONST'; import type {Dimensions} from '@src/types/utils/Layout'; -import {defaultTheme} from './theme'; import colors from './theme/colors'; import type {ThemeColors} from './theme/types'; import addOutlineWidth from './utils/addOutlineWidth'; @@ -6559,8 +6558,5 @@ const styles = (theme: ThemeColors) => type ThemeStyles = ReturnType; -const defaultStyles = styles(defaultTheme); - export default styles; -export {defaultStyles}; export type {ThemeStyles, StatusBarStyle, ColorScheme, AnchorPosition, AnchorDimensions, OverlayStylesParams}; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index edc6ec42d00a..6273944db661 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -9,8 +9,6 @@ import {LETTER_AVATAR_COLOR_OPTIONS} from '@libs/Avatars/PresetAvatarCatalog'; import {isMobile, isMobileChrome} from '@libs/Browser'; import getPlatform from '@libs/getPlatform'; import {hashText} from '@libs/UserUtils'; -// eslint-disable-next-line no-restricted-imports -import {defaultTheme} from '@styles/theme'; import colors from '@styles/theme/colors'; import type {ThemeColors} from '@styles/theme/types'; import variables from '@styles/variables'; @@ -18,7 +16,6 @@ import CONST from '@src/CONST'; import type {Transaction} from '@src/types/onyx'; import type {Dimensions} from '@src/types/utils/Layout'; import type Nullable from '@src/types/utils/Nullable'; -import {defaultStyles} from '..'; import type {ThemeStyles} from '..'; import shouldPreventScrollOnAutoCompleteSuggestion from './autoCompleteSuggestion'; import getCardStyles from './cardStyles'; @@ -2291,8 +2288,5 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ type StyleUtilsType = ReturnType; -const DefaultStyleUtils = createStyleUtils(defaultTheme, defaultStyles); - export default createStyleUtils; -export {DefaultStyleUtils}; export type {StyleUtilsType, AvatarSizeName};