createSwitchNavigator with React Navigation v5? - javascript

I my old version I make something like this:
going to splash screen, if user is connected go to App, else go to login.
And I can navigate into screen by using this.props.navigation.navigate("Register")
SplashScreen.js :
componentDidMount(){
firebase.auth().onAuthStateChanged(user => {
this.props.navigation.navigate(user ? "App" : "Login")
});
}
in App.js
const Container = createAppContainer(
createSwitchNavigator(
{
Splash: SplashScreen,
Login: LoginScreen,
Register: RegisterScreen,
App: AppContainer,
},
{
initialRouteName: "Splash",
}
)
);
//Other code
render(){
return (<Container/>)
}
Now I try to use react Navigation v5 but everything seem to be more complicated.
My App.js look like this :
export default function App() {
const [isDarkTheme, setIsDarkTheme] = React.useState(false);
const theme = isDarkTheme ? CombinedDarkTheme : CombinedDefaultTheme; // Use Light/Dark theme based on a state
function toggleTheme() {
// We will pass this function to Drawer and invoke it on theme switch press
setIsDarkTheme(isDark => !isDark);
}
return (
<PaperProvider theme={theme}>
<NavigationContainer theme={theme}>
<Drawer.Navigator
drawerContent={props => <DrawerContent {...props} toggleTheme={toggleTheme}/>}
>
<Drawer.Screen
name="HomeDrawer"
component={MainTabScreen}
/>
<Drawer.Screen
name="SettingsScreen"
component={SettingsStackScreen}
/>
</Drawer.Navigator>
</NavigationContainer>
</PaperProvider>
);
}
How I'm suppose to do something like this but with PaperProvider ?

You could simply do your API Call and depending on the result, display or not the routes. Something like :
import { useEffect } from 'react';
export default function App() {
const [isDarkTheme, setIsDarkTheme] = React.useState(false);
const [isAuthed, setIsAuthed] = React.useState(false);
useEffect(async () => setIsAuthed(await getIsAuthed()));
const theme = isDarkTheme ? CombinedDarkTheme : CombinedDefaultTheme; // Use Light/Dark theme based on a state
function toggleTheme() {
// We will pass this function to Drawer and invoke it on theme switch press
setIsDarkTheme((isDark) => !isDark);
}
const defineRoutes = () => {
if (!isAuthed) {
return <Drawer.Screen name='Login' component={Login} /> // Don't know your login comp name, but you got the idea
}
// Don'tknow if Fragment exists on react native and is usable here, just try
return (
<Fragment>
<Drawer.Screen name='HomeDrawer' component={MainTabScreen} />
<Drawer.Screen name='SettingsScreen' component={SettingsStackScreen} />
</Fragment>
);
};
return (
<PaperProvider theme={theme}>
<NavigationContainer theme={theme}>
<Drawer.Navigator
drawerContent={(props) => (
<DrawerContent {...props} toggleTheme={toggleTheme} />
)}
>
{defineRoutes()}
</Drawer.Navigator>
</NavigationContainer>
</PaperProvider>
);
}

Related

React Native, Drawer navigation not show, the app is closes

When I try to access the Drawer the app closes and does not show me any type of error, I have not been able to solve the problem and be able to view the Draw.
This function is the one that generates problems for me, it should be noted that I am new to react and it is most likely that I have an error in the navigation configuration.
const LoggedInNavigator = () => (
<LoggedInStack.Navigator>
<LoggedInStack.Screen name="Home" component={Home} options={homeOptions} />
</LoggedInStack.Navigator>
);
I share the complete navigation file
import * as React from 'react';
import { NavigationContainer, Theme } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { useSelector } from 'react-redux';
import { navigationRef } from './NavigationService';
import Login from 'app/screens/Login';
import Home from 'app/screens/Home';
import ForgotPassword from 'app/screens/ForgotPassword';
import ThemeController from '../components/ThemeController';
import { StatusBar, Text } from 'react-native';
import { ILoginState } from 'app/models/reducers/login';
const Stack = createStackNavigator();
const AuthStack = createStackNavigator();
const LoggedInStack = createDrawerNavigator();
const homeOptions = {
title: 'Home',
headerTitleStyle: {
fontWeight: 'bold',
},
headerRight: () => <ThemeController />,
};
interface IState {
loginReducer: ILoginState;
}
interface IProps {
theme: Theme;
}
const AuthNavigator = () => {
const isLoggedIn = useSelector(
(state: IState) => state.loginReducer.isLoggedIn,
);
return (
<AuthStack.Navigator>
<Stack.Screen
name="Login"
component={Login}
options={{
// When logging out, a pop animation feels intuitive
// You can remove this if you want the default 'push' animation
animationTypeForReplace: isLoggedIn ? 'push' : 'pop',
headerRight: () => <ThemeController />,
}}
/>
<Stack.Screen
name="ForgotPassword"
component={ForgotPassword}
options={{
// When logging out, a pop animation feels intuitive
// You can remove this if you want the default 'push' animation
animationTypeForReplace: isLoggedIn ? 'push' : 'pop',
headerRight: () => <ThemeController />,
}}
/>
</AuthStack.Navigator>
);
};
//This component is the problem, but I don't know how to fix it
const LoggedInNavigator = () => (
<LoggedInStack.Navigator>
<LoggedInStack.Screen name="Home" component={Home} options={homeOptions} />
</LoggedInStack.Navigator>
);
const App: React.FC<IProps> = (props: IProps) => {
const { theme } = props;
const isLoggedIn = useSelector(
(state: IState) => state.loginReducer.isLoggedIn,
);
return (
<NavigationContainer ref={navigationRef} theme={theme}>
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
<Stack.Navigator screenOptions={{ headerShown: false }}>
{isLoggedIn ? (
<Stack.Screen
name="AppHome"
component={LoggedInNavigator}
// options={homeOptions}
options={{ headerShown: false }}
/>
) : (
<Stack.Screen
name="Login"
component={AuthNavigator}
options={{
// When logging out, a pop animation feels intuitive
// You can remove this if you want the default 'push' animation
animationTypeForReplace: isLoggedIn ? 'push' : 'pop',
headerRight: () => <ThemeController />,
}}
/>
)}
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
thanks for your help

How to nest Context and MUI's theme provider?

So I currently have a state to toggle dark / light mode on a website with lots of nested components. I have a root App.js:
function App() {
return (
<DarkModeProvider>
<div className="App">
<HomePage />
</div>
</DarkModeProvider>
);
}
The DarkModeProvider is my react context, and in the next component I have a layout where I have my navigation and routing, that is wrapped in the ThemeProvider:
const HomePage = () => {
const { isDarkTheme } = useContext(DarkModeContext);
return (
<ThemeProvider theme={isDarkTheme ? createTheme(darkTheme) :
createTheme(lightTheme)}>
<DrawerProvider>
<Router>
<Box sx={{ display: "flex" }}>
<Box>
<HomePageInner />
</Box>
<Routes>
<Route path="/inventory" element={<Inventory />} />
<Route path="/orders" element={<Orders />} />
<Route path="/vendors" element={<Vendors />} />
</Routes>
</Box>
</Router>
</DrawerProvider>
</ThemeProvider>
);
};
It works fine, however, I'd like to access the theme context in my "app" class that's in the root App component. If I wrap the DarkModeProvider with the ThemeProvider, I don't have access to the state of the dark / light mode, if I wrap the ThemeProvider with the DarkModeProvider, I lose access to the isDarkTheme state from my context.
Is there a better practice to format this? What I really want is to have a css / style sheet in the source folder as the same level as the app component. I'm unsure how to access my theme provider when it's not in my app component. OR how to have my dark mode state accessible while wrapped inside of the theme provider (or vice versa).
For example my App.CSS:
body {
background-color: theme.primary.palette.main;
/* I would like the body to follow my MUI theme. */
}
a {
color: inherit;
text-decoration: none;
}
Dark Mode Provider:
import { createContext, useState } from "react";
const DarkModeContext = createContext();
export const DarkModeProvider = ({ children }) => {
const [isDarkTheme, setIsDarkTheme] = useState(false);
const changeTheme = () => {
setIsDarkTheme(!isDarkTheme);
};
return (
<DarkModeContext.Provider
value={{
isDarkTheme,
changeTheme,
}}
>
{children}
</DarkModeContext.Provider>
);
};
export default DarkModeContext;
You can move the ThemeProvider component inside App.js file and have a state there for isDarkTheme which you can then use both for DarkModeProvider and ThemeProvider
function App() {
const [isDarkTheme, setIsDarkTheme] = useState(false);
const changeTheme = () => {
setIsDarkTheme(!isDarkTheme);
};
return (
<DarkModeProvider value={{ isDarkTheme, changeTheme }}>
<ThemeProvider theme={isDarkTheme ? createTheme(darkTheme) : createTheme(lightTheme)}>
<div className="App">
<HomePage />
</div>
</ThemeProvider>
</DarkModeProvider>
);
}
Dark Mode Provider:
import { createContext } from "react";
const DarkModeContext = createContext();
export const DarkModeProvider = ({ children, value }) => {
return (
<DarkModeContext.Provider value={value}>
{children}
</DarkModeContext.Provider>
);
};
export default DarkModeContext;

How to get React State update in different file to use SwitchNavigator?

I'm still pretty new to React Native and this may need a simple solution and/or I'm going about this completely wrong.
I want to create a terms and condition page. So in my TermsAndCondition.js I've created a isAccepted = React.useState(false) and then I import it to App.js.
When reloading App.js for the first time, it will have the state of isAccepted = false. My button works when changing the state to isAccepted = true But it doesn't update it in App.js to allow me to use the "SwitchNavigator" to switch to a different set of screens.
I just want to be able to have my terms and condition screen but by itself and not being part of a stack to go back. Is there a way to get isAccepted to be updated in another file or to get this same idea going?
Following my code.
App.js
const Stack = createStackNavigator();
export default class App extends React.Component {
render() {
return (
<NavigationContainer>
{!isAccepted ? (
<Stack.Navigator>
<Stack.Screen
name="Terms and Conditions"
component={TermsAndConditions}
/>
</Stack.Navigator>
) : (
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="mapListScreen" component={mapListScreen} />
<Stack.Screen name="mapScreen1" component={mapScreen1} />
</Stack.Navigator>
)}
</NavigationContainer>
);
}
}
TermsandCondition.js
function TermsAndConditions() {
const [isAccepted, setIsAccepted] = React.useState(false);
const [isActive, setIsActive] = React.useState(false);
const handleAcceptInput = () => {
setIsAccepted(!isAccepted);
};
const handleActiveInput = () => {
setIsActive(!isActive);
};
return (
<View style={styles.container}>
<Text style={styles.title}>Terms and conditions</Text>
<ScrollView
style={styles.tcContainer}
onScroll={({ nativeEvent }) => {
if (isCloseToBottom(nativeEvent)) {
handleActiveInput();
}
}}
>
<Text style={styles.tcP}>Some text</Text>
</ScrollView>
<TouchableOpacity
disabled={!isActive}
onPress={() => handleAcceptInput()}
style={isActive ? styles.button : styles.buttonDisabled}
>
<Text style={styles.buttonLabel}>Accept</Text>
</TouchableOpacity>
</View>
);
}
export default TermsAndConditions;
You can't access your state in your Stack Navigator like this you have to use React-Redux for this.

How do you use React context inside App.js file?

I am building a multi-language app. I am using react-intl. So far, so good. I made a state of the language with context api, so I can switch it easily. However I get this error when I try to use the state in App.js: TypeError: Object is not iterable (cannot read property Symbol(Symbol.iterator)).
Here is my context file:
import React, {useState, createContext} from 'react'
export const LanguageContext = createContext();
export const LanguageProvider = (props) => {
const [language, setLanguage] = useState('')
return (
<LanguageContext.Provider value = {[language,setLanguage]}>
{props.children}
</LanguageContext.Provider>
)
}
And here is the App.js:
function App() {
const [language, setLanguage] = useContext(LanguageContext)
return (
<LanguageProvider>
//i tried using locale={language}
<I18nProvider locale={LOCALES.language}>
<CartProvider>
<TableProvider>
<div className="App">
<Router>
<Header />
<Switch>
<Route path='/Cart' component={Cart} />
<Route path='/:group/:subGroup/:item' component={Item} />
<Route path='/:group/:subGroup' component={Items} />
<Route path='/' exact component={Home} />
<Route path='/:group' component={Groups} />
</Switch>
</Router>
</div>
</TableProvider>
</CartProvider>
</I18nProvider>
</LanguageProvider>
);
}
export default App
Here is the locale file that Im using to pass to the I18nProvider :
export const LOCALES = {
ENGLISH : 'en',
FRENCH: 'fr'
}
And where I change the context value(another component, not App.js):
const [language, setLanguage] = useContext(LanguageContext)
following line is cut from jsx:
onClick={() => setLanguage('en')}
I thinks the problem might be because I am trying to access the context before the App.js return statement, where the provider wraps the children but even if this is the case, I still don't know what might fix it. Any help would be appreciated!
I thinks the problem might be because I am trying to access the context before the App.js return statement
You're right this is the problem.
Depending on where you want to use useContext you could create an extra component that is a child of LanguageProvider. Then inside this child you are able to use useContext.
To give a simplified example:
const App = () => {
const [language, setLanguage] = useContext(LanguageContext);
useEffect(() => {
setLanguage('en');
}, []);
return <p>{language}</p>;
};
export default function AppWrapper() {
return (
<LanguageProvider>
<App />
</LanguageProvider>
);
}
I had the same problem trying to apply an authentication flow with react-navigation v5. I tried to follow the documentation as it is:
Authentication flows with react-navigation v5 But when trying to mix it with Context I run into the same issue
As in the previous Answer, I solve it in the same way.
Here it's an example where it has 3 possible Screens Stacks:
I create a component where I'll be using the context
const RootStack = () => {
const { state } = useContext(AuthContext);
return (
<Stack.Navigator>
{false ? (
<Stack.Screen name="Splash" component={SplashScreen} />
) : state.token === null ? (
<Stack.Screen
name="Authentication"
component={AuthenticationStack}
options={{
title: 'Sign in',
headerShown: false,
animationTypeForReplace: false ? 'pop' : 'push',
}}
/>
) : (
<Stack.Screen name="Home" component={AppStack} />
)}
</Stack.Navigator>
);
};
And then I insert the component inside the provider:
export default ({ navigation }) => {
return (
<AuthProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="RootStack"
component={RootStack}
options={{
headerShown: false,
}}
/>
</Stack.Navigator>
</NavigationContainer>
</AuthProvider>
);
};

How to nest Stack Navigator inside a Drawer Navigator with navigation v5+

my project structure is something like:
app.js (Loads data and a drawer navigator)
MainScreen (Load cards using the data fetched before in a preview mode and when you click you have a NewsComponent that fetch the complete data for that record)
NewsScreen (Load a full article using the data fetched before)
in my App.js I'm using a drawer navigator like that:
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Main">
<Drawer.Screen name="Main" options={{ headerShown: false}}>
{props => <MainScreen {...props} extraData={App.news} />}
</Drawer.Screen>
<Drawer.Screen name="Court Reservation" component={CourtReservation}></Drawer.Screen>
<Drawer.Screen name="News" component={NewsScreen}></Drawer.Screen>
</Drawer.Navigator>
</NavigationContainer>
);
}
but I need to insert an stack navigator inside the Main component in order to show the records (NewsScreen) because if not when I go back to the list (Main) and again to a different record the content is not updating and the first record is being shown.
I tried several times but I'm getting all sort of errors. Right now my MainScreen goes like this:
render() {
return (
<Container>
<Header>
<Left>
<Button onPress={() => this.props.navigation.openDrawer()} transparent>
<Icon name='menu' />
</Button>
</Left>
<Body>
<Title>Header</Title>
</Body>
<Right />
</Header>
<FlatList
data={this.state.news}
onEndReached={this.fetchMoreNews}
onEndReachedThreshold={0.5}
keyExtractor={(item, index) => index.toString()}
renderItem={({item}) => (
<Content>
<CardNewsComponent data={item} nav={this.props.navigation}/>
</Content>
)}/>
</Container>
);
}
But is using the drawer navigation ir order to "Navigate" to the component.
How can I integrate a stack navigator to do so?
EDIT:
After the kind awnser my app.js is like:
import * as React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { createStackNavigator } from '#react-navigation/stack';
import MainScreen from './screens/MainScreen';
import NewsScreen from './screens/NewsScreen';
import CourtReservation from './screens/CourtReservation';
import * as SplashScreen from 'expo-splash-screen';
import * as Font from 'expo-font';
import { Ionicons } from '#expo/vector-icons';
import axios from 'axios';
const Drawer = createDrawerNavigator();
const Stack = createStackNavigator();
const myStack = (<Stack.Navigator initialRouteName="Main">
<Stack.Screen name="Main" options={{ headerShown: false}}>
{props => <MainScreen {...props} extraData={App.news} />}
</Stack.Screen>
<Stack.Screen name="News" component={NewsScreen}></Stack.Screen>
</Stack.Navigator>)
export default class App extends React.Component {
state = {
appIsReady: false,
};
news = {};
async componentDidMount() {
// Prevent native splash screen from autohiding
try {
await SplashScreen.preventAutoHideAsync();
} catch (e) {
console.warn(e);
}
this.prepareResources();
}
/**
* Method that serves to load resources and make API calls
*/
prepareResources = async () => {
await performAPICalls();
await downloadAssets();
this.setState({ appIsReady: true }, async () => {
await SplashScreen.hideAsync();
});
};
render() {
if (!this.state.appIsReady) {
return null;
}
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Main">
<Drawer.Screen name="Main" component={myStack}></Drawer.Screen>
<Drawer.Screen name="Court Reservation" component={CourtReservation}></Drawer.Screen>
</Drawer.Navigator>
</NavigationContainer>
);
}
}
// Put any code you need to prepare your app in these functions
async function performAPICalls() {
await axios.get('https://alqueriadelbasket.com/?r=noticias/FetchNoticiasJson&boundBot=0&boundTop=5')
.then((response) => {
App.news = response.data;
}).catch((error)=> {
console.log(error);
})
}
async function downloadAssets() {
await Font.loadAsync({
Roboto: require('native-base/Fonts/Roboto.ttf'),
Roboto_medium: require('native-base/Fonts/Roboto_medium.ttf'),
...Ionicons.font,
});
}
And my main Screen
import React, { Component } from 'react';
import { Container, Content, Header, Left, Button, Icon, Right, Body, Title} from 'native-base';
import {FlatList} from 'react-native';
import CardNewsComponent from './components/CardNewsComponent';
import axios from 'axios';
export default class MainScreen extends Component {
constructor(props){
super(props);
this.state = {
news: this.props.extraData,
boundBot: 6,
bountTop: 11,
error: null,
};
}
fetchMoreNews = () => {
axios.get(`https://alqueriadelbasket.com/?r=noticias/FetchNoticiasJson&boundBot=${this.state.boundBot}&boundTop=${this.state.bountTop}`)
.then((response) => {
this.setState({
boundBot: this.state.boundBot+5,
boundTop: this.state.boundTop+5,
news: this.state.news.concat(response.data)
})
}).catch((error)=> {
console.log(error);
})
}
newsData = this.props.extraData;
render() {
return (
<Container>
<Header>
<Left>
<Button onPress={() => this.props.navigation.openDrawer()} transparent>
<Icon name='menu' />
</Button>
</Left>
<Body>
<Title>Header</Title>
</Body>
<Right />
</Header>
<FlatList
data={this.state.news}
onEndReached={this.fetchMoreNews}
onEndReachedThreshold={0.5}
keyExtractor={(item, index) => index.toString()}
renderItem={({item}) => (
<Content>
<CardNewsComponent data={item} nav={this.props.navigation}/>
</Content>
)}/>
</Container>
);
}
}
The error is "Got an invalid value for 'component' prop for the screen Main, It must be a valid react component.
Funny thing that Main was working like a charm before... :/
EDIT2:
After try and look inside of the official doc.
This was the problem:
It can't be a const, it have to be a function... So I'll added the function to the drawer nav and it worked like a charm.
here's the code:
function MyStack() {
return (
<Stack.Navigator initialRouteName="Main">
<Stack.Screen name="Main" options={{ headerShown: false}}>
{props => <MainScreen {...props} extraData={App.news} />}
</Stack.Screen>
<Stack.Screen name="News" component={NewsScreen}></Stack.Screen>
</Stack.Navigator>
);
}
Inside the drawer navigator, we have drawer screens
<Drawer.Navigator initialRouteName="Main">
<Drawer.Screen name="Main" options={{ headerShown: false}}>
......
//you can add a Stack Navigator as a screen here
</Drawer.Navigator>
So you can define a Stack navigator above as such:
const MyStack = <Stack.Navigator>
//your stack screens
......
</Stack.Navigator>
Then, use it as a drawer screen:
<Drawer.Screen name="main" component={MyStack} />

Categories