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 InterstellarSentinel199

How can I centralize dynamic theming and responsive styles in React Native without repeatedly calling useStyles?

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

Goal:
I want a performant React Native theme manager/changer where styles update dynamically based on the current theme (dark/light) and device dimensions (e.g., handling orientation changes). My styles are defined in a separate styles.ts file and used like <View style={styles.screen}>, but I currently have to call const styles = useStyles(); in every component.

Problem:
I want to avoid or hide these repeated hook calls across components, so that I can simply import and use the styles directly while still ensuring they update correctly when the theme or dimensions change.

Current 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', }, }); };

Question:
How can I avoid calling the useStyles hook in every component while still ensuring that my styles update dynamically with theme and dimension changes? Suggestions like using a context provider, higher‑order component, or creating custom styled components are welcome.

2

Comments 0

Answers 3

Views 46

3 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

Below is one approach to “hiding” the per‑component hook call while still getting updated styles on theme or dimension changes. The key idea is to create a centralized StylesProvider (or HOC) that computes the styles on each update and makes them available via context. Then, components can either use a HOC that injects the styles via props or—if you’re okay with a one‑line context lookup—call a simple useContext hook (which can be hidden in your own custom HOC).

Below is an example using a context provider:


styles.ts

TYPESCRIPT
import React, { createContext, useContext, useMemo } from 'react'; import { StyleSheet, StyleProp, ViewStyle, TextStyle } from 'react-native'; import { useThemeStore, colors } from './themeStore'; import { useDimensionsStore } from './dimensionsStore'; interface AppStyles { screen: StyleProp<ViewStyle>; container: StyleProp<ViewStyle>; text: StyleProp<TextStyle>; header: StyleProp<TextStyle>; button: StyleProp<ViewStyle>; buttonText: StyleProp<TextStyle>; } const StylesContext = createContext<AppStyles | null>(null); export const StylesProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const theme = useThemeStore((state) => state.theme); const { width, height } = useDimensionsStore(); const themeColors = colors[theme]; // Memoize styles so they are recalculated only when dependencies change. const styles = useMemo(() => { return StyleSheet.create<AppStyles>({ 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> ); }; // A custom hook that simply reads from the context. export const useStyles = (): AppStyles => { const context = useContext(StylesContext); if (!context) { throw new Error('useStyles must be used within a StylesProvider'); } return context; };

Now, wrap your app (or at least the navigators/screens that require dynamic styles) with the provider. For example:


App.tsx

TYPESCRIPT
import React from 'react'; 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 { StylesProvider, useStyles } from './styles'; type RootStackParamList = { Home: undefined; Settings: undefined; }; const Stack = createStackNavigator<RootStackParamList>(); function HomeScreen({ navigation }: StackScreenProps<RootStackParamList, 'Home'>) { // Now using styles from context 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 ( <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> ); }

If you’d like to completely remove the need to call a hook inside every screen, you can wrap your screen components with a higher‑order component that injects the styles automatically. For example:


withDynamicStyles.tsx

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

Then your screen component can be defined without calling the hook:

TYPESCRIPT
import React from 'react'; import { View, Text, Button, TouchableOpacity } from 'react-native'; type HomeScreenProps = { styles: ReturnType<typeof useStyles>; navigation: any; // or your proper navigation prop type }; function HomeScreenBase({ styles, navigation }: HomeScreenProps) { 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={() => { /* theme toggle logic */ }} /> <TouchableOpacity style={[styles.button, { marginTop: 16 }]} onPress={() => navigation.navigate('Settings')} > <Text style={styles.buttonText}>Go to Settings</Text> </TouchableOpacity> </View> </View> ); } export const HomeScreen = withDynamicStyles(HomeScreenBase);

In summary, because the styles need to update when your theme or dimensions change, you must obtain them at render time. Wrapping this logic in a context provider or a higher‑order component lets you “hide” the per‑component hook calls while still benefiting from dynamic, performant styling.

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 EclipseWayfarer239 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 PulsarVoyager346 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.