Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by QuantumGuardian834

How can I centralize dynamic theme and responsive style management in React Native without repeatedly calling hooks?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

Goal: Create a high-performance theme manager/changer in React Native that supports dynamic theme colors (e.g., dark/light) and responsive dimensions updating automatically on changes (such as switching between portrait and landscape).

Current Usage: I'm styling components like <View style={styles.screen}> by importing styles from a styles.ts file. In that file I calculate styles based on the current theme and screen dimensions. However, in each component I have to call the hook with const styles = useStyles(); and I want to avoid doing this repeatedly.

What I've Tried: I currently use the following implementation:

App.tsx

TYPESCRIPT
import { NavigationContainer, DefaultTheme } from '@react-navigation/native'; import { createStackNavigator, StackScreenProps } from '@react-navigation/stack'; import { Button, View, Text, TouchableOpacity } from 'react-native'; import { useThemeStore, colors } from './themeStore'; import { useStyles } from './styles'; type RootStackParamList = { Home: undefined; Settings: undefined; }; const Stack = createStackNavigator<RootStackParamList>(); function HomeScreen({ navigation }: StackScreenProps<RootStackParamList, 'Home'>) { const styles = useStyles(); const toggleTheme = useThemeStore(state => state.toggleTheme); return ( <View style={styles.screen}> <View style={[styles.container, { marginTop: 40 }]}> <Text style={styles.header}>Home Screen</Text> <Text style={styles.text}>Current theme settings</Text> </View> <View style={styles.container}> <Button title="Toggle Theme" onPress={toggleTheme} /> <TouchableOpacity style={[styles.button, { marginTop: 16 }]} onPress={() => navigation.navigate('Settings')} > <Text style={styles.buttonText}>Go to Settings</Text> </TouchableOpacity> </View> </View> ); } function SettingsScreen({ navigation }: StackScreenProps<RootStackParamList, 'Settings'>) { const styles = useStyles(); const theme = useThemeStore(state => state.theme); return ( <View style={styles.screen}> <View style={styles.container}> <Text style={styles.header}>Settings Screen</Text> <Text style={styles.text}>Current Theme: {theme.toUpperCase()}</Text> <TouchableOpacity style={[styles.button, { marginTop: 16 }]} onPress={() => navigation.goBack()} > <Text style={styles.buttonText}>Go Back</Text> </TouchableOpacity> </View> </View> ); } export default function App() { const theme = useThemeStore(state => state.theme); const navigationTheme = { ...DefaultTheme, dark: theme === 'dark', colors: { ...DefaultTheme.colors, ...colors[theme], }, }; return ( <NavigationContainer theme={navigationTheme}> <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: navigationTheme.colors.card, }, headerTintColor: navigationTheme.colors.text, }} > <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Settings" component={SettingsScreen} /> </Stack.Navigator> </NavigationContainer> ); }

themeStore.ts

TYPESCRIPT
import { MMKVLoader } from 'react-native-mmkv-storage'; import {create} from 'zustand'; import { persist } from 'zustand/middleware'; import { storage } from './storage'; export const storage = new MMKVLoader() .withInstanceID('themeStorage') .initialize(); export const colors = { light: { primary: '#007AFF', background: '#FFFFFF', card: '#FFFFFF', text: '#000000', border: '#D3D3D3', notification: '#FF3B30', }, dark: { primary: '#BB86FC', background: '#121212', card: '#1E1E1E', text: '#FFFFFF', border: '#383838', notification: '#CF6679', }, }; interface ThemeState { theme: 'light' | 'dark'; toggleTheme: () => void; } export const useThemeStore = create<ThemeState>()( persist( (set) => ({ theme: 'light', toggleTheme: () => set((state) => ({ theme: state.theme === 'light' ? 'dark' : 'light' })), }), { name: 'theme-storage', storage: { getItem: async (name) => { const value = storage.getString(name); return value ? JSON.parse(value) : null; }, setItem: async (name, value) => { storage.setString(name, JSON.stringify(value)); }, removeItem: async (name) => { storage.removeItem(name); }, }, } ) );

styles.ts

TYPESCRIPT
import { useThemeStore } from './themeStore'; import { useDimensionsStore } from './dimensionsStore'; import { StyleSheet } from 'react-native'; import { colors } from './themeStore'; export const useStyles = () => { const theme = useThemeStore(state => state.theme); const { width, height } = useDimensionsStore(); const themeColors = colors[theme]; return StyleSheet.create({ screen: { flex: 1, backgroundColor: themeColors.background, width, height, }, container: { padding: 16, backgroundColor: themeColors.card, borderRadius: 8, margin: 8, }, text: { color: themeColors.text, fontSize: 16, }, header: { color: themeColors.primary, fontSize: 24, fontWeight: 'bold', }, button: { backgroundColor: themeColors.primary, padding: 12, borderRadius: 8, alignItems: 'center', }, buttonText: { color: themeColors.background, fontWeight: 'bold', }, }); };

Desired Outcome: Find a way to centralize the hook logic (or hide it) such that I can import and use styles in components without having to call useStyles() every time, thereby keeping component code clean and maintainable.

2

Comments 0

Answers 3

Views 43

3 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

Below is one common solution: instead of calling useStyles (and therefore using hooks) in every component, you can “lift” the style calculation into a provider (or an HOC) that makes the computed styles available via a React Context. This way, you only need to wrap your app (or a subtree) once, and then your components can either consume the context or be wrapped by an HOC so that they don’t need to explicitly call your hook every time.

Because React hooks must be called during render inside function components (or custom hooks), you can’t directly export a static “styles” object that responds to dynamic theme and dimensions changes. Instead, you can create a provider that computes the styles (using useThemeStore and useDimensionsStore) and passes them down.

Below is an example implementation:


Creating a Styles Provider and Context

TYPESCRIPT
// ThemeStylesContext.tsx import React, { createContext, useContext, useMemo, ReactNode } from 'react'; import { StyleSheet } from 'react-native'; import { useThemeStore, colors } from './themeStore'; import { useDimensionsStore } from './dimensionsStore'; type StylesType = ReturnType<typeof StyleSheet.create>; const StylesContext = createContext<StylesType | null>(null); export const StylesProvider = ({ children }: { children: ReactNode }) => { const theme = useThemeStore(state => state.theme); const { width, height } = useDimensionsStore(); const themeColors = colors[theme]; // Memoize the computed styles – these update when theme, width, or height changes. const styles = useMemo(() => { return StyleSheet.create({ screen: { flex: 1, backgroundColor: themeColors.background, width, height, }, container: { padding: 16, backgroundColor: themeColors.card, borderRadius: 8, margin: 8, }, text: { color: themeColors.text, fontSize: 16, }, header: { color: themeColors.primary, fontSize: 24, fontWeight: 'bold', }, button: { backgroundColor: themeColors.primary, padding: 12, borderRadius: 8, alignItems: 'center', }, buttonText: { color: themeColors.background, fontWeight: 'bold', }, }); }, [theme, width, height, themeColors]); return ( <StylesContext.Provider value={styles}> {children} </StylesContext.Provider> ); }; // Custom hook for consuming the styles. export const useThemeStyles = () => { const styles = useContext(StylesContext); if (!styles) { throw new Error('useThemeStyles must be used within a StylesProvider'); } return styles; };

Option A: Using a Custom Hook to Consume the Styles

Then in any component, instead of calling useStyles (your previous hook) you can simply call the new hook:

TYPESCRIPT
// HomeScreen.tsx import React from 'react'; import { Button, View, Text, TouchableOpacity } from 'react-native'; import { useThemeStyles } from './ThemeStylesContext'; import { useThemeStore } from './themeStore'; import { StackScreenProps } from '@react-navigation/stack'; type RootStackParamList = { Home: undefined; Settings: undefined; }; function HomeScreen({ navigation }: StackScreenProps<RootStackParamList, 'Home'>) { const styles = useThemeStyles(); const toggleTheme = useThemeStore(state => state.toggleTheme); return ( <View style={styles.screen}> <View style={[styles.container, { marginTop: 40 }]}> <Text style={styles.header}>Home Screen</Text> <Text style={styles.text}>Current theme settings</Text> </View> <View style={styles.container}> <Button title="Toggle Theme" onPress={toggleTheme} /> <TouchableOpacity style={[styles.button, { marginTop: 16 }]} onPress={() => navigation.navigate('Settings')} > <Text style={styles.buttonText}>Go to Settings</Text> </TouchableOpacity> </View> </View> ); } export default HomeScreen;

Option B: Using a Higher-Order Component (HOC)

If you’d rather not call a hook inside every component, you can create an HOC that injects the computed styles as a prop:

TYPESCRIPT
// withThemeStyles.tsx import React from 'react'; import { useThemeStyles } from './ThemeStylesContext'; export const withThemeStyles = <P extends { styles: ReturnType<typeof useThemeStyles> }>( Component: React.ComponentType<P> ) => (props: Omit<P, 'styles'>) => { const styles = useThemeStyles(); return <Component {...(props as P)} styles={styles} />; };

Then you can wrap your component:

TYPESCRIPT
// SettingsScreen.tsx import React from 'react'; import { Text, TouchableOpacity, View } from 'react-native'; import { withThemeStyles } from './withThemeStyles'; import { useThemeStore } from './themeStore'; import { StackScreenProps } from '@react-navigation/stack'; type RootStackParamList = { Home: undefined; Settings: undefined; }; type Props = StackScreenProps<RootStackParamList, 'Settings'> & { styles: ReturnType<typeof useThemeStyles>; }; function SettingsScreen({ navigation, styles }: Props) { const theme = useThemeStore(state => state.theme); return ( <View style={styles.screen}> <View style={styles.container}> <Text style={styles.header}>Settings Screen</Text> <Text style={styles.text}>Current Theme: {theme.toUpperCase()}</Text> <TouchableOpacity style={[styles.button, { marginTop: 16 }]} onPress={() => navigation.goBack()} > <Text style={styles.buttonText}>Go Back</Text> </TouchableOpacity> </View> </View> ); } export default withThemeStyles(SettingsScreen);

Usage in Your App

Wrap your navigation tree (or the portion of your app that needs dynamic styles) with the provider.

TYPESCRIPT
// App.tsx import React from 'react'; import { NavigationContainer, DefaultTheme } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import HomeScreen from './HomeScreen'; import SettingsScreen from './SettingsScreen'; import { useThemeStore, colors } from './themeStore'; import { StylesProvider } from './ThemeStylesContext'; type RootStackParamList = { Home: undefined; Settings: undefined; }; const Stack = createStackNavigator<RootStackParamList>(); export default function App() { const theme = useThemeStore(state => state.theme); const navigationTheme = { ...DefaultTheme, dark: theme === 'dark', colors: { ...DefaultTheme.colors, ...colors[theme], }, }; return ( <StylesProvider> <NavigationContainer theme={navigationTheme}> <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: navigationTheme.colors.card, }, headerTintColor: navigationTheme.colors.text, }} > <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="Settings" component={SettingsScreen} /> </Stack.Navigator> </NavigationContainer> </StylesProvider> ); }

Summary

• Due to React’s rules of hooks you can’t simply import a dynamic, reactive styles object from a module without a hook call.
• By calculating your styles in a provider (or HOC) you centralize the hook usage.
• Components then only use a minimal hook (or get styles injected as props) without repeating the full style logic.

This approach maintains performance (with memoization via useMemo) and makes your theme and dimension responsiveness available in every component without cluttering each component with repetitive hook calls.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by InterstellarKeeper575 1 month ago

0

Once you want to have the ability to switch colors dynamically based on the dark or light mode value in your store, it needs to be a hook to trigger a render cycle.

The only thing that comes into my mind is a higher order component. Something like withStyles() that you can wrap around your component. But this has to be done on each and every component / screen again.

The HOC would look somewhat like this:

TYPESCRIPT
export const withStyles = <T,>(WrappedComponent: React.FC<T & styles: StyleSheet.NamedStyles<any>>) => { return (props: T) => { const styles = useStyles() // here you pass `styles` as an additional prop into your wrapped component return ( <WrappedComponent {...props} styles={styles} /> ) } }

Typing is probably not correct the way it is. But with that you could wrap a component like this:

TYPESCRIPT
const HomeScreen = withStyles<React.FC<StackScreenProps<RootStackParamList, 'Home'>>>(({ navigation, styles }) { // styles gets passed in here as prop const toggleTheme = useThemeStore(state => state.toggleTheme); return ( <View style={styles.screen}> <View style={[styles.container, { marginTop: 40 }]}> <Text style={styles.header}>Home Screen</Text> <Text style={styles.text}>Current theme settings</Text> </View> <View style={styles.container}> <Button title="Toggle Theme" onPress={toggleTheme} /> <TouchableOpacity style={[styles.button, { marginTop: 16 }]} onPress={() => navigation.navigate('Settings')} > <Text style={styles.buttonText}>Go to Settings</Text> </TouchableOpacity> </View> </View> ); }

No comments yet.

Answer by CometRanger801 1 month ago

0

If you find that you write code something like <View style={styles.screen}>... a lot, then why not abstract this in to something like a custom styled component?

Example:

TYPESCRIPT
const ViewScreen = ({ children, style, ...props }: ViewScreenProps) => { const styles = useStyles(); return ( <View style={[styles.screen, style]} {...props}> {children} </View> ); };
TYPESCRIPT
function HomeScreen( { navigation }: StackScreenProps<RootStackParamList, 'Home'> ) { const toggleTheme = useThemeStore(state => state.toggleTheme); return ( <ViewScreen>{ ... }</ViewScreen> ); }

Or abstract the styling into a variant prop.

Example:

TYPESCRIPT
const StyledText = ({ variant, style, ...props }) => { const styles = useStyles(); const textStyle = style[variant]; return <Text style={[textStyle, style]} {...props} />; };

Example:

TYPESCRIPT
function HomeScreen( { navigation }: StackScreenProps<RootStackParamList, 'Home'> ) { const toggleTheme = useThemeStore(state => state.toggleTheme); return ( <ViewScreen> <ViewContainer> <StyledText variant="header">Home Screen</StyledText> <StyledText variant="text">Current theme settings</StyledText> </ViewContainer> <ViewContainer> <Button title="Toggle Theme" onPress={toggleTheme} /> <StyledTouchableOpacity style={{ marginTop: 16 }} onPress={() => navigation.navigate('Settings')} > <StyledText variant="buttonText">Go to Settings</StyledText> </StyledTouchableOpacity> </ViewContainer> </ViewScreen> ); }

No comments yet.

Discussion

No comments yet.