Hello React Native experts,
I am facing an issue with conditional navigation in my React Native app where switching between navigators based on a state leads to a blank screen. Here are the details:
Problem Description:
My app is structured to conditionally render one of two navigators (OnboardingNavigator or TabNavigator) based on an isOnboarded state. Despite the OnboardingStart screen loading perfectly when directly navigated from App.js (when hardcoded), integrating a condition to switch between the navigators results in a blank screen.
Behavior Observed:
If App.js directly sets OnboardingNavigator, OnboardingStart works without issues. When using the conditional logic based on isOnboarded, it results in a blank screen even though console logs confirm that isOnboarded is being retrieved correctly and is set to false.
Questions:
Could there be an issue related to state updates or conditional rendering in React Native that might be causing this? What debugging strategies or practices can I use to further diagnose and resolve this issue?
Any insights or advice on how to handle this kind of navigation setup would be greatly appreciated!
import React, { useState, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import OnboardingNavigator from './navigation/OnboardingNavigator';
import TabNavigator from './navigation/TabNavigator';
const App = () => {
const [isOnboarded, setIsOnboarded] = useState(null);
useEffect(() => {
const checkOnboarding = async () => {
try {
const onboarded = await AsyncStorage.getItem('@onboarded');
console.log('Onboarded Status:', onboarded);
setIsOnboarded(onboarded === 'true');
} catch (error) {
console.error('Failed to fetch onboarding status:', error);
}
};
checkOnboarding();
}, []);
return (
<NavigationContainer>
{isOnboarded ? <TabNavigator /> : <OnboardingNavigator setIsOnboarded={setIsOnboarded} />}
</NavigationContainer>
);
};
export default App;
This is OnboardingNavigator:
import { createStackNavigator } from '@react-navigation/stack';
import { Button, Alert } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import OnboardingStart from '../screens/onboarding/OnboardingStart';
import OnboardingLogin from '../screens/onboarding/OnboardingLogin';
import OnboardingLoginEmail from '../screens/onboarding/OnboardingLoginEmail';
import OnboardingBorn from '../screens/onboarding/OnboardingBorn';
import OnboardingHourly from '../screens/onboarding/OnboardingHourly';
import OnboardingGoal from '../screens/onboarding/OnboardingGoal';
import OnboardingSaving from '../screens/onboarding/OnboardingSaving';
import OnboardingSavingThings from '../screens/onboarding/OnboardingSavingThings';
import OnboardingSavingHardwork from '../screens/onboarding/OnboardingSavingHardwork';
import OnboardingSavingStopping from '../screens/onboarding/OnboardingSavingStopping';
import OnboardingSavingRetirement from '../screens/onboarding/OnboardingSavingRetirement';
import OnboardingTime from '../screens/onboarding/OnboardingTime';
import OnboardingTimeBuyBack from '../screens/onboarding/OnboardingTimeBuyBack';
import OnboardingTimeWork from '../screens/onboarding/OnboardingTimeWork';
import OnboardingTracker from '../screens/onboarding/OnboardingTracker';
import OnboardingPaywall from '../screens/onboarding/OnboardingPaywall';
const OnboardingStack = createStackNavigator();
function OnboardingNavigator({ setIsOnboarded }) {
const handleCompleteOnboarding = async () => {
try {
await AsyncStorage.setItem('@onboarded', 'true');
setIsOnboarded(true);
} catch (error) {
console.error("Failed to set onboarding complete:", error);
Alert.alert("Error", "Failed to complete onboarding. Please try again.");
}
};
return (
<OnboardingStack.Navigator screenOptions={{ headerShown: false }}>
<OnboardingStack.Screen name="OnboardingStart" component={OnboardingStart} />
<OnboardingStack.Screen name="OnboardingLogin" component={OnboardingLogin} />
<OnboardingStack.Screen name="OnboardingLoginEmail" component={OnboardingLoginEmail} />
<OnboardingStack.Screen name="OnboardingBorn" component={OnboardingBorn} />
<OnboardingStack.Screen name="OnboardingHourly" component={OnboardingHourly} />
<OnboardingStack.Screen name="OnboardingGoal" component={OnboardingGoal} />
<OnboardingStack.Screen name="OnboardingSaving" component={OnboardingSaving} />
<OnboardingStack.Screen name="OnboardingSavingThings" component={OnboardingSavingThings} />
<OnboardingStack.Screen name="OnboardingSavingHardwork" component={OnboardingSavingHardwork} />
<OnboardingStack.Screen name="OnboardingSavingStopping" component={OnboardingSavingStopping} />
<OnboardingStack.Screen name="OnboardingSavingRetirement" component={OnboardingSavingRetirement} />
<OnboardingStack.Screen name="OnboardingTime" component={OnboardingTime} />
<OnboardingStack.Screen name="OnboardingTimeBuyBack" component={OnboardingTimeBuyBack} />
<OnboardingStack.Screen name="OnboardingTimeWork" component={OnboardingTimeWork} />
<OnboardingStack.Screen name="OnboardingTracker" component={OnboardingTracker} />
<OnboardingStack.Screen
name="OnboardingPaywall"
component={OnboardingPaywall}
options={{
headerRight: () => (
<Button onPress={handleCompleteOnboarding} title="Done" />
)
}}
initialParams={{ setIsOnboarded: setIsOnboarded }} // Pass the setIsOnboarded down as a param
/>
</OnboardingStack.Navigator>
);
}
export default OnboardingNavigator;
and this is TabNavigator:
import { View, StyleSheet, TouchableOpacity } from 'react-native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons'; // Ensure this is installed
import Main from '../screens/Main';
import Lifetime from '../screens/Lifetime';
import Settings from '../screens/Settings';
import { createStackNavigator } from '@react-navigation/stack';
const Tab = createBottomTabNavigator();
// Custom Tab Bar Component
const CustomTabBar = ({ state, descriptors, navigation }) => {
return (
<View style={styles.tabBarContainer}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const isFocused = state.index === index;
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
const getIconName = (routeName) => {
switch (routeName) {
case 'Lifetime':
return 'home-outline';
case 'Main':
return 'cash-outline';
case 'Settings':
return 'person-outline';
default:
return 'ellipse-outline';
}
};
return (
<TouchableOpacity
key={route.name}
accessibilityRole="button"
accessibilityState={isFocused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
style={styles.tabButton}
>
<Ionicons
name={getIconName(route.name)}
size={31}
color={isFocused ? 'black' : 'gray'} // Black for active, gray for inactive
/>
</TouchableOpacity>
);
})}
</View>
);
};
// **Main Tab Navigator Component**
const TabNavigator = () => (
<Tab.Navigator
initialRouteName="Main"
screenOptions={{
headerShown: false,
}}
tabBar={(props) => <CustomTabBar {...props} />}
>
<Tab.Screen name="Lifetime" component={Lifetime} />
<Tab.Screen name="Main" component={Main} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
);
export default TabNavigator;
// **Styles**
const styles = StyleSheet.create({
tabBarContainer: {
flexDirection: 'row',
height: 80,
backgroundColor: 'transparent', // Transparent background
alignItems: 'center',
justifyContent: 'space-around', // Evenly distribute buttons
position: 'absolute',
bottom: 25,
left: 0,
right: 0,
borderTopWidth: 1, // Add a thin line
borderTopColor: '#ccc', // Color of the line (light gray in this case)
},
tabButton: {
alignItems: 'center',
justifyContent: 'center',
flex: 1, // Equal space for each button
},
});