React navigation 5 stack navigator and drawer navigator together - react-native

Basically I am from react-navigation v4, I am familiar handling the same with SwitchNavigator as bridge, but v5 does no support for SwitchNavigator, so bit struggling to understand the below implementation.
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
function AuthenticationStack({ navigation }) {
return (
<Stack.Navigator initialRouteName="Home" screenOptions={{headerShown: false}}>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Registration" component={Registration} />
</Stack.Navigator>
);
}
function UserStack({ navigation }) {
return (
<Stack.Navigator initialRouteName="Jobs" screenOptions={{headerShown: false}}>
<Stack.Screen name="Jobs" component={Profile} />
<Stack.Screen name="JobDetails" component={JobDetails} />
</Stack.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Drawer.Navigator drawerContent={props => <SideBar { ...props } />}>
<Drawer.Screen name="Home" component={AuthenticationStack} />
<Drawer.Screen name="Jobs" component={UserStack} />
</Drawer.Navigator>
</NavigationContainer>
);
}
export default App;
The above code works but have some problems like,
While loading the app, initially the drawer is visible and goes hidden when the app loaded.
I do not want to have drawers for Authentication screens, If I have 2 different navigation without splitting AuthenticationStack and UserStack then I am facing problem while navigating from Login to Profile and vice versa
UPDATE 1
export default() => {
const [logged, setUser] = React.useState(false);
return (
<NavigationContainer>
{
logged
?
<DrawerScreen initialParams={{ setUser }} />
:
<AuthStackScreen initialParams={{ setUser }} />
}
</NavigationContainer>
);
}
Now in login.js, I need to update setUser to true, right? If yes how can I access setUser in my login.js file
Update 2
class Login extends Component {
fetch(login_url, {
method: "POST",
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Accept-Language': 'en-US' },
body: JSON.stringify(data)
})
.then((response) => {
const success = response.ok;
const data = response.json();
return Promise.all([success, data]);
})
.then(([success, response]) => {
var data, userInfo;
if (success) {
data = {
token: response.token,
user: response.user,
}
if(_storeUserData(response)) {
_retrieveUserData().then((userInfo) => {
this.setState({
logged: true,
userInfo: userInfo,
loading:false,
email: '',
password: ''
});
navigate('Profile');
})
.catch((error) => {
// alert(error);
});
}
}
this.setState(state_data);
})
.catch((error) => {
alert('Error:'+error);
this.setState({ loading: false});
});
}

You can create something similar to the switch navigator by conditionally rendering navigators like below.
export default function App() {
const [loggedIn, setLoggedIn] = React.useState(false);
return (
<NavigationContainer>
{loggedIn ? (
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} initialParams={{ setLoggedIn }}/>
<Drawer.Screen name="Notifications" component={NotificationsScreen} />
</Drawer.Navigator>
) : (
<Stack.Navigator>
<Stack.Screen
name="Auth"
component={AuthScreen}
initialParams={{ setLoggedIn }}
/>
</Stack.Navigator>
)}
</NavigationContainer>
);
}
You can tryout this snack for a working example
https://snack.expo.io/#guruparan/switchsample

Related

Navigate between two stack Navigators after login in react native

This is my first stack
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="Home" component={MainScreen} />
<Stack.Screen name="Schools" component={SchoolsScreen} />
<Stack.Screen name="Setting" component={SettingScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Saved" component={SavedData} />
<Stack.Screen name="Profile2" component={Profile2} />
<Stack.Screen name="SchoolDetails" component={SchoolDetailsScreen} />
<Stack.Screen name="Bottom" component={BottomTab} />
</Stack.Navigator>
</NavigationContainer>
This is my second stack
<NavigationContainer>
<Stack.Navigator
initialRouteName="Login"
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen name="Login" component={AuthLogin} />
<Stack.Screen name="Register" component={AuthRegister} />
<Stack.Screen name="Forget" component={ForgetPassword} />
</Stack.Navigator>
</NavigationContainer>
THis is my app.js file
const [auth, setAuth] = useState(false);
useEffect(() => {
(async () => {
const value = await AsyncStorage.getItem("isLoggedin");
console.log(value);
setAuth(value);
})();
}, []);
return (
<SafeAreaView style={{ flex: 1 }}>
<NativeBaseProvider config={config}>
{auth == "true" ? <InsideStack /> : <OutsideStack />}
</NativeBaseProvider>
</SafeAreaView>
);
}
I want to navigate to home screen from login screen , after function call
// navigation.push("Home");
const storeData = async (value) => {
try {
await AsyncStorage.setItem("isLoggedin", JSON.stringify(true));
} catch (e) {
// saving error
}
};
storeData();
navigation.push("Home");
}}
But i got error:
Do you have a screen named 'Home'?
this is the simple code for this. you have to use redux or context api for gloal state management. here is a min example ;
import {NavigationContainer} from '#react-navigation/native';
import React from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {GetUserContacts} from '../logic/getcontact';
import {setContacts} from '../store/actions/ContactActions';
import AuthNavigation from './AuthNavigation';
import RootNavigation from './RootNavigation';
const AppContainer = () => {
const {is_logged_in} = useSelector(state => state.persistedReducer);
const dispatch = useDispatch();
if (is_logged_in) {
GetUserContacts('PK').then(data => {
dispatch(setContacts(data));
});
}
return (
<NavigationContainer>
{is_logged_in ? <RootNavigation /> : <AuthNavigation />}
</NavigationContainer>
);
};
export default AppContainer;
after user logged in you have to make that is_logged_in true. so the navigator changes itself. you will have to persist this so when application restart the user dont need to login again

How can i notify to App for what reload the app to show another screen?

I am in login screen i pressed the button login so
that button have this action
store.dispatch({
type: 'login',
payload: {
name: email.value
},
})
on the reducers change the value
export default function (state, action) {
switch (action.type) {
case 'login':
return {
...state,
user: action.payload,
isLogged:true
}
default:
return newState
}
}
now i have to notify to App.js for the new change because that is depend to show the stack.screen
export default function App() {
return (
<Provider store={store} theme={theme}>
<Tabs store={store} />
</Provider>
)
}
How can i notify to App for what reload the app to show another screen?
but never my component is updated, i was trying to update my component using useEffect but not
function Tabs ({ user }) {
const {
data: { isLogged },
} = useSelector((state) => state)
useEffect(() => {
console.log(1234546)
}, [isLogged, user])
return (
<Stack.Navigator >
{ !store.getState().data.isLogged ?
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/> :
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>}
</Stack.Navigator>
)
When working with States Or Redux We don't have to tell the app about when to re-render the JSX code.
It will be re-renders automatically whenever the change occurs in redux or state.
But, We must have to be assured that we are not mutating the data in our state or redux, We always have to assign a new object to state or redux.
You can try the below changes in your code,
function Tabs ({ user }) {
const { isLogged } = useSelector((state) => state?.data)
return (
<Stack.Navigator >
{
isLogged
?
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/>
:
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>
}
</Stack.Navigator>
)
}
App navigator need to subscribe isLogged state then rerender accordingly.
import { useEffect } from "react";
import { useSelector } from "react-redux";
const App = () => {
const isLogged = useSelector((state) => state.isLogged);
// Track authentication state
useEffect(() => {}, [isLogged]);
return (
<Stack.Navigator>
{!isLogged ? (
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/>
) : (
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>
)}
</Stack.Navigator>
);
};

Need help on navigation errors on react native app

I keep getting an error when trying to navigate to Home after register and login. This is the error: The action 'NAVIGATE' with payload {"name":"Home"} was not handled by any navigator. This is how I am navigating to Home after registration: navigation.navigate("Home");
Would love some advice or guidance.
function HomeStack() {
return (
<Stack.Navigator>
<Stack.Screen name="HomeScreen" component={HomeScreen} />
</Stack.Navigator>
);
}
function ProfileStack() {
return (
<Stack.Navigator>
<Stack.Screen name="ProfileScreen" component={ProfileScreen} />
</Stack.Navigator>
);
}
function LeaderboardStack() {
return (
<Stack.Navigator>
<Stack.Screen name="LeaderboardScreen" component={LeaderboardScreen} />
</Stack.Navigator>
);
}
const getPage = (user) => {
if (user) {
return (
<>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: () => {
if (route.name == "Home") {
return <Entypo name="home" size={24} color="black" />;
} else if (route.name == "Leaderboard") {
return (
<MaterialIcons name="leaderboard" size={24} color="black" />
);
} else if (route.name == "Profile") {
return (
<Ionicons name="person-circle" size={24} color="black" />
);
}
},
})}
>
<Tab.Screen
name="Home"
component={HomeStack}
options={{ tabBarLabel: "Home" }}
/>
<Tab.Screen
name="Leaderboard"
component={LeaderboardStack}
options={{ tabBarLabel: "Leaderboard" }}
/>
<Tab.Screen
name="Profile"
component={ProfileStack}
options={{ tabBarLabel: "Profile" }}
/>
</Tab.Navigator>
</NavigationContainer>
</>
)
} else {
return (
<Stack.Navigator>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Registration" component={RegistrationScreen} />
</Stack.Navigator>
)
}
}
export default function App() {
const [loading, setLoading] = useState(true);
const [user, setUser] = useState(null);
useEffect(() => {
const usersRef = firebase.firestore().collection("users");
firebase.auth().onAuthStateChanged((user) => {
if (user) {
usersRef
.doc(user.uid)
.get()
.then((document) => {
const userData = document.data();
setLoading(false);
setUser(userData);
})
.catch((error) => {
setLoading(false);
});
} else {
setLoading(false);
}
});
}, []);
if (loading) {
return <></>;
}
return (
<NavigationContainer>
{getPage(user)}
</NavigationContainer>
);
}
There are lots of things happening here, sorry to say but your coding is mess, i can give you example how can manage it cleanly, however all i can see herr is that you tab which contains your homestack and a screen you put in stack above tab both have the same name "Home" change your stack name with homestack and try navigating there..

How to navigate to other screen after the whole app has loaded?

Good day! Currently learning Nav 5 and so far this is what I have tried. I want to navigate to other screens depending on the result of the Firebase login check.
But what happens is the app renders 3times in the Splash screen, what I want is the 3rd or the final data. How can I know when the app is done re-rendering stuff.
This is what I have tried and I am fairly new to React navigation hopefully somebody could point me on how to do this properly. Thank you!
APP.JS
useEffect(() => {
const subscriber = firebase.auth().onAuthStateChanged(user => {
let logged = false
if (user) {
logged = true
firebase.firestore().collection('users')
.doc(user.uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
return
}
const user = firestoreDocument.data()
console.log("subs data", user)
setUser(user)
})
.catch(error => {
alert(error)
})
} else {
setUser(null)
logged = false
}
setIsLoggedIn(logged)
setIsLogIncheckDone(true)
})
return subscriber // unsubscribe on unmount
}, [])
const getSomething = async () => {
const data = {
isLoggedIn, user, isLogInCheckDone
}
return data
}
if (!isLogInCheckDone) {
return <SplashScreen />
}
return (
<AppContext.Provider value={{ GetSomething: getSomething }}>
<ThemeContextProvider>
<ThemeContextConsumer>
{context =>
(
<NavigationContainer>
<Stack.Navigator theme={context.theme ? "dark" : "light"} >
<Stack.Screen name={"Splash"} component={SplashScreen} options={{ headerShown: false }} />
<Stack.Screen name={"Common"} component={CommonStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Auth"} component={AuthStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Dashboard"} component={DashboardStack} options={{ headerShown: false }} />
<Stack.Screen name={"Verify"} component={VerificationStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Wizard"} component={ProfileWizardStack} options={{ headerShown: false }} />
</Stack.Navigator>
<Toast ref={(ref) => Toast.setRef(ref)} />
</NavigationContainer>
)
}
</ThemeContextConsumer>
</ThemeContextProvider>
</AppContext.Provider>
)
}
SPLASHSCREEN.JS
export default function SplashScreen({ navigation, route }) {
const appContext = useContext(AppContext)
useEffect(() => {
const newData = appContext?.GetSomething()
console.log("Splashy", newData)
//TODO Navigate here
}, [appContext])
return (
<View style={styles.container}>
<LottieView source={newSource} autoPlay loop />
</View>
)
}
You can use the logged state in your app.js to render what you want. Try something like this:
return (
<NavigationContainer >
{logged ? <Screens you want to render if the user is logged /> : <SplashScreen />}
</NavigationContainer>
);

Navigating between different stackscreen in react-navigation 5

isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
)
How can I navigate from signInScreen to HomeScreen.
You can make a context to handle the authentification flow and on your app launch check if a token or anything is already set.
const Index = () => {
const [authState, dispatch] = useAuthContext();
useEffect(() => {
const boostrapAsync = async () => {
let userToken = null;
try {
userToken = await AsyncStorage.getItem('userToken');
} catch (e) {
console.log('Restoring token failed');
}
restoreToken({ token: userToken }, dispatch);
};
boostrapAsync();
}, []);
if (authState.isLoading) {
return (
<SplashScreen />
);
}
return (
<Root>
<NavigationContainer>
<UIProvider reducer={UIReducer} initialState={initialStateUIReducer}>
{authState.userToken ? (
<UserProvider reducer={UserReducer} initialState={initialStateUserReducer}>
<AppNavigation />
</UserProvider>
) : <AuthNavigation />}
</UIProvider>
</NavigationContainer>
</Root>
);
};
export default Index;
These problems can be solved with a splash screen. I think you should collect all screens under one tag. Then, make a splash screen. After that, you can control the parameter is called isSignedIn in the SplashScreen. Then you navigate the screen which you want.