New to React Native I recently followed the navigation tutorial https://www.robinwieruch.de/react-native-navigation/. I then decided to put the sign out function into the draw navigator which opened up a whole can of worms around passing props. I’ve read so much stuff on that but still can’t figure out what’s going on here, and hope someone can explain it to me.
Without copying the whole code (and I hope I've copied enough), inside App() I have the following:
const [isAuthenticated, setIsAuthenticated] = React.useState(false);
const handleSignIn = () => {
// TODO implement real sign in mechanism
setIsAuthenticated(true);
};
const handleSignOut = () => {
// TODO implement real sign out mechanism
setIsAuthenticated(false);
};
…
<Stack.Screen
name="Home"
component={() => <HomeDrawer someText="Mick" handleSignOut={handleSignOut}/>}
…
Outside of App() I have the following:
const HomeDrawer = (props) => {
console.log("In HomeDrawer props = " + JSON.stringify(props));
return (
<Drawer.Navigator
screenOptions={{ headerShown: false }}
initialRouteName="Home Page"
drawerContent={(props) => <CustomDrawerSignOut {...props}/>}
>
<Drawer.Screen name="Home Page" component={HomePage} />
<Drawer.Screen name="Account Details" component={AccountScreen} />
</Drawer.Navigator>
);
};
const CustomDrawerSignOut = (props) => {
console.log("In CustomDrawerSignOut props = " + JSON.stringify(props));
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem label="Sign Out" onPress={props.handleSignOut} />
</DrawerContentScrollView>
);
}
The log output then gives me the following:
In HomeDrawer props = {"someText":"Mick"}
In CustomDrawerSignOut props = {"state":{"stale":false,"type":"drawer","key":"drawer-hyj-tged8DkJ7nDxNVVWB","index":0,"routeNames":["Home Page","Account Details"],"history":[{"type":"route","key":"Home Page-rf91ybSgClEeaz_f7Qf7a"}],"routes":[{"name":"Home Page","key":"Home Page-rf91ybSgClEeaz_f7Qf7a"},{"name":"Account Details","key":"Account Details-hwXHxAxa5wR-lg9xq7cp3"}],"default":"closed"},"navigation":{},"descriptors":{"Home Page-rf91ybSgClEeaz_f7Qf7a":{"route":{"name":"Home Page","key":"Home Page-rf91ybSgClEeaz_f7Qf7a"},"navigation":{},"options":{"headerShown":false}},"Account Details-hwXHxAxa5wR-lg9xq7cp3":{"route":{"name":"Account Details","key":"Account Details-hwXHxAxa5wR-lg9xq7cp3"},"navigation":{},"options":{"headerShown":false}}}}
'handleSignOut' doesn’t seem to get passed to HomeDrawer and 'someText' doesn’t seem to get passed to CustomDrawerSignOut which does receive a lot of other props. Why are 'someText' and 'handleSignOut' not being passed properly, and how do I fix it?
Thanks
I looks like CustomDrawerSignOut is not being passed the props from the HomeDrawer
Please check the components passed to
<Stack.Screen
name="Home"
component={(props) => <HomeDrawer {...props} someText="Mick" handleSignOut={handleSignOut} />}
....
const HomeDrawer = (props) => {
console.log("In HomeDrawer props = " + JSON.stringify(props));
return (
<Drawer.Navigator
screenOptions={{ headerShown: false }}
initialRouteName="Home Page"
// here
drawerContent={(drawerProps) => <CustomDrawerSignOut {...drawerProps} handleSignOut={props.handleSignOut}/>}
>
<Drawer.Screen name="Home Page" component={HomePage} />
<Drawer.Screen name="Account Details" component={AccountScreen} />
</Drawer.Navigator>
);
};
Hope it solves the problem
Related
How can I execute the same function on each screen with React Navigation? I'm using React Native and TypeScript. I have a navigation with home, profile, etc...
I have the below function and I want it to be executed when you navigate to profile, home, etc.
const getFunction = (): void => {
console.log("execute function");
};
I tried the below code but it doesn't work:
useEffect(() => {
const listener = navigation.addListener("focus", (e) => {
getFunction();
});
return () => listener;
}, [navigation]);
An overview of my screens:
return (
...
<Stack.Screen
name='home'
component={home}
/>
<Stack.Screen
name='profile'
component={profile}
/>
...
)
Please if you could help me I would really appreciate it!! :)
You can use screenListeners prop on the navigator. This way you can specify listeners for events from all screens. As example like so:
<Stack.Navigator
screenListeners={{
focus: (e) => {
// code you wanna execute goes here
console.log("Focus has changed")
},
}}
>
<Stack.Screen name="home" component={home} />
<Stack.Screen name="profile" component={profile} />
</Stack.Navigator>
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.
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>
);
};
The layout component is rendered on all pages.
I want to achieve the following
in /items page
*Layout component is displayed if the user is admin
* Layout component not displayed if the user is non-admin
below is my code,
function Main() {
const isAdmin = getUser();
return(
<Switch>
<Route
exact
path="/items"
render={routeProps => (
<Layout>
{isAdmin ? <Items {...routeProps} />: <NotFound/>}
</Layout>
)}
/>
<Route
exact
path="/home"
render={routeProps => (
<Layout>
<Home {...routeProps} />
</Layout>
)}
/>
</Switch>
);
}
const Layout: React.FC = ({ children }) => (
<>
<TopBar />
{children}
<BottomBar />
</>
);
As you see from the above code, the Layout component is displayed in all pages and is used as a wrapper for other routes too like for /home
Now I don't want the Layout component to be displayed only in /items page if a user is not admin
What I have tried?
const Layout: React.FC = ({ children }) => {
const history = useHistory();
const isItemsPath = history.location.pathname.includes('/items');
const isAdmin = getUser();
return (
<>
{!isItemsPath && <TopBar />
{children}
{!isItemsPath && <BottomBar />
</>
);
}
But this will not display TopBar and BottomBar if the items page even if the user is admin. how can I modify the condition
such that TopBar and BottomBar are displayed in all pages except items page if not admin.
could someone help me with this? thanks.
};
In your layout component you can use conditional rendering. We can check if the page is isItemsPath first, if it is items path and user is not admin then we do not show the Topbar and BottomBar, for all other pages we show them
const Layout: React.FC = ({ children }) => {
const history = useHistory();
const isItemsPath = history.location.pathname.includes('/items');
const isAdmin = getUser();
return !(isItemsPath && !isAdmin) ?
<>
{children}
</> : (
<>
<TopBar />
{children}
<BottomBar />
</>
);
}
What about you change the condition in Route?
<Route
exact
path="/items"
render={routeProps => (
{isAdmin ? <Layout>
<Items {...routeProps} />
</Layout>
: <NotFound/>}
)}
/>
If I understand correctly, you might be looking for something along the lines of this:
const Layout: React.FC = ({ children }) => {
const history = useHistory();
const isItemsPath = history.location.pathname.includes('/items');
const isAdmin = getUser();
return (
<>
{(!isItemsPath || isAdmin) && <TopBar />
{children}
{(!isItemsPath || isAdmin) && <BottomBar />
</>
);
Then you should be able to remove your isAdmin condition in your Main component.
You have some choice to doing this, but I think the best way is using HOC if you have to repeat checking the user is admin or not, pass your component to HOC and in HOC component check if a user is an admin or not. You can use this HOC component for all of your components. In HOC component use conditional rendering. Something like this :
function checkAdmin(WrappedComponent, selectData) {
const isAdmin = getUser();
render() {
return ({isAdmin} ? <WrappedComponent /> : <div></div>)
}
}
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>
);
}