I would like to know how I would go about implementing a welcome/getting started screen using react navigation v3.
My confusion would be where the welcome/getting started screen would go?
should the screen be in the Appstack or Authstack?
I want to display this to new users only. When a user logs out and re-authenticate I want it to be popped out of the stack because they are not new users and take them directly to the main app.
I think this piece of logic should take place in the Authloadingscreen, am just not sure how or what technique to use.
This is an example Appfrom https://snack.expo.io/#react-navigation/auth-flow-v3
Any help would be appreciated, thanks.
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
Button,
StatusBar,
StyleSheet,
View,
} from 'react-native';
import { createStackNavigator, createSwitchNavigator, createAppContainer } from 'react-navigation';
class SignInScreen extends React.Component {
static navigationOptions = {
title: 'Please sign in',
};
render() {
return (
<View style={styles.container}>
<Button title="Sign in!" onPress={this._signInAsync} />
</View>
);
}
_signInAsync = async () => {
await AsyncStorage.setItem('userToken', 'abc');
this.props.navigation.navigate('App');
};
}
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome to the app!',
};
render() {
return (
<View style={styles.container}>
<Button title="Show me more of the app" onPress={this._showMoreApp} />
<Button title="Actually, sign me out :)" onPress={this._signOutAsync} />
</View>
);
}
_showMoreApp = () => {
this.props.navigation.navigate('Other');
};
_signOutAsync = async () => {
await AsyncStorage.clear();
this.props.navigation.navigate('Auth');
};
}
class OtherScreen extends React.Component {
static navigationOptions = {
title: 'Lots of features here',
};
render() {
return (
<View style={styles.container}>
<Button title="I'm done, sign me out" onPress={this._signOutAsync} />
<StatusBar barStyle="default" />
</View>
);
}
_signOutAsync = async () => {
await AsyncStorage.clear();
this.props.navigation.navigate('Auth');
};
}
class AuthLoadingScreen extends React.Component {
constructor() {
super();
this._bootstrapAsync();
}
// Fetch the token from storage then navigate to our appropriate place
_bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('userToken');
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
this.props.navigation.navigate(userToken ? 'App' : 'Auth');
};
// Render any loading content that you like here
render() {
return (
<View style={styles.container}>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
const AppStack = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
export default createAppContainer(createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
));
I would put this in the AppStack, since it is part of your app content and not part of your authentication flow.
Additionally, you need a way to determine if it's a new user or a returning user. So either you store this information server side, or locally using AsyncStorage. The best approach would be to store this information server side, since a user can always get a new phone. So during loading (if authenticated) or authenticating you make sure you fetch that data and display/hide the welcome screen accordingly.
Related
How The App works!
I have an app that shows a list of names and after that shows the meaning of the Name depends on some preference the user choose it from a dropDowns.
So in total there is a Main of 3 Screens!
First: is the UI screen that asks user to choose data,
Second: is the screen that holds a list contains names, I list them using a FlatList (BTW the data retrives from a SqLite -not important-)
Third: is the Screen that shows the name's meaning,
I am navigating from the "Main-Screen" to the "NameList-Screen" then To the "NameMeaning-Screen",
The navigation to the "NameMeaning-Screen" done by pressing on an item from the List.
Till here the app working nicely!
I decided to add a new feature to the app and that to show all the names i have in my DB and show it in a screen that have two Tabs, The first tab shows male's names and the second tab shows the female's names.
I did that step too.
But What am facing now is!
I want to navigate from that tabs when pressing on an item from the flatList, and navigate to the "Third Screen" I mentioned above the name's meaning!
But it gave error that, there is no such screen called "NameMeaing" and then it says " Name screen Its not handled by any navigator", So as much as i understand, when am in the Tabs the program have no access to the stack Navigator and cuz of this it gives that error.
As much as i was able to find on Web, There is examples of navigating from tabs to another stack screens, but in the all the examples, the tabs were the main screen in the applicaiton, but in my case i reach the tabs after pressing a certain button and navigate to another stacked screen.
As a soluation for the problem i thought about creating a secondry stack navigator inside my Tabs.js file that contains the tabs but i couldnt, then i thought i should create a Tab navigator in my App.js adding it to my stack navigator that already exists there and compine them in a navigator container. Maybe this is the soluation but i couldnt complete the code and connect the dots. Any help please?!
This is a video of the app while working (The scenario)
https://youtu.be/dBLNF5zMCt0
This is the Error it shows:
Error when i try to navigate from a tab screen to another different screen
This is App.js file
import 'react-native-gesture-handler';
import React, {Component} from 'react';
import {createStackNavigator} from 'react-navigation-stack';
import {createAppContainer} from 'react-navigation';
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs';
import MainScreen from './MainScreen';
import NamesList from './NamesList';
import NameMeaning from './NameMeaning';
import NameListWithTabsAgirlAboy from './NameListWithTabsAgirlAboy';
const App = createStackNavigator(
{
MainScreen: {
screen: MainScreen,
navigationOptions: {
header: null,
},
},
NamesList: {
screen: NamesList,
navigationOptions: {
header: null,
},
},
NameListWithTabsAgirlAboy: {
screen: NameListWithTabsAgirlAboy,
navigationOptions: {
header: null,
},
},
NameMeaning: {
screen: NameMeaning,
navigationOptions: {
header: null,
},
},
},
{
initialRouteName: 'MainScreen',
}
);
export default createAppContainer(App);
This is NameMeaninng.js file
class NameMeaning extends Component {
constructor(props) {
super(props);
}
render() {
const {navigation} = this.props;
return(
<SafeAreaView style= {styles.container}>
<Text style={styles.title}>معنى اسم {JSON.stringify(navigation.getParam('nameTitle', 'NO-ID'))}</Text>
<ScrollView style={styles.scrollView}>
<Text style={styles.text}>
{JSON.stringify(navigation.getParam('explaination', 'NO-ID'))}
</Text>
</ScrollView>
</SafeAreaView>
);
}
}
this is parts of Tabs.js file
This file have three classes in totall. BoysScreen, GirlsScreen and Tabs classes..
class BoysScreen extends React.Component {
constructor(props) {
super(props);
const {navigation} = this.props;
}
render() {
let FlatListNames = [];
FlatListNames = boysNames();
const {navigation} = this.props;
function Item({ title }, {navigation}) {
return (
<View style = {StyleSheet.item}>
<Text styel = {styles.title}> {title} </Text>
<Text style ={StyleSheet.explain}>اضغط للشرح</Text>
</View>
)
}
function boysNames() {
var boysNamesList = [];
db.transaction(tx => {
// boys names
tx.executeSql('SELECT ID, Name, Explanation FROM Names WHERE NameSex=?', ["لـ طفلي"], (tx, results) => {
for (let i = 0; i < results.rows.length; ++i) {
boysNamesList.push(results.rows.item(i));
}
});
}); // DB transaction
return boysNamesList;
};
return(
<View style= {styles.container}>
<FlatList
data={FlatListNames}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) =>
<TouchableOpacity
onPress = {() => {this.props.navigation.navigate('NameMeaning',{
nameTitle : item.Name,
nameId : item.ID,
explaination : item.Explanation,
});
}}
>
<Item title = {item.Name}/>
</TouchableOpacity>
}
/>
</View>
);
}
}// ends of BoysScreen Class
class Tabs extends React.Component {
constructor(props){
super(props);
}
render() {
const Tab = createMaterialTopTabNavigator();
// I have tried to create a stack here but it gave errors and couldnt solve it
//cont Stack = createStackNavigator();
return(
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name ="FemaleNames" component = {GirlsScreen} /> //GirlsScreen is a class
<Tab.Screen name = "MaleNames" component = {BoysScreen} /> // BoysScreen is a class
</Tab.Navigator>
// I have tried to import NameMeanig class and navigate to it like this, but it gaves errors too.
//<Stack.Screen name="Home" component={NameMeaning} />
</NavigationContainer>
);
}
Thanks in advance, any help of how i can build this algorithm is appricated really..
It was a problem with version compatibility, i was using React navigation V4 and V5 alltogether, After Searching more i have solved it in this way, Changing App.js and compine all the stacks and tabs navigators in App.js
Credit to this guy : https://www.youtube.com/watch?v=nQVCkqvU1uE
import 'react-native-gesture-handler';
import React, {Component} from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs';
import MainScreen from './App/components/Home/MainScreen';
import NamesList from './App/components/NameList/NamesList';
import NameMeaning from './App/components/NameMeaning/NameMeaning';
import GirlNamesScreen from './App/components/NameList/GirlsNamesTab';
import BoysNamesScreen from './App/components/NameList/BoysNamesTab';
const MainStack = createStackNavigator();
const MainStackScreen = () => (
<MainStack.Navigator>
<MainStack.Screen
name="MainScreen"
component={MainScreen}
options={{ title: "Main Screen title" }}
/>
<MainStack.Screen
name="NamesList"
component={NamesList}
options={{ title: "NamesList Screen title" }}
/>
<MainStack.Screen
name="NameMeaning"
component={NameMeaning}
options={{ title: "NameMeaning Screen title" }}
/>
<MainStack.Screen
name="TabsScreen"
component={TabsScreen}
options={{ title: "TabsScreen Screen title" }}
/>
</MainStack.Navigator>
);
const Tabs = createMaterialTopTabNavigator();
const GirlNamesStack = createStackNavigator();
const BoysNamesStack = createStackNavigator();
const GirlNamesStackScreen = () => (
<GirlNamesStack.Navigator>
<GirlNamesStack.Screen name="GirlsNames" component={GirlNamesScreen} />
</GirlNamesStack.Navigator>
);
const BoysNamesStackScreen = () => (
<BoysNamesStack.Navigator>
<BoysNamesStack.Screen name="BoysNames" component={BoysNamesScreen} />
</BoysNamesStack.Navigator>
);
const TabsScreen = () => (
<Tabs.Navigator>
<Tabs.Screen name="BoysNames" component={BoysNamesStackScreen} />
<Tabs.Screen name="GirlsNames" component={GirlNamesStackScreen} />
</Tabs.Navigator>
);
const RootStack = createStackNavigator();
const RootStackScreen = () => (
<RootStack.Navigator headerMode="none">
<RootStack.Screen
name="Main"
component={MainStackScreen}
options={{
animationEnabled: false
}}
/>
</RootStack.Navigator>
);
export default () => {
return (
<NavigationContainer>
<RootStackScreen/>
</NavigationContainer>
);
};
I copied fallowing code from a github project and tried using expo. The project executed without error but when i press button nothing happens. not even error this is my code
NB- I stetted an alert inside onChooseImagePress and alert is working fine
import React from 'react';
import { Image, StyleSheet, Button, Text, View, Alert, } from 'react-native';
import { ImagePicker } from 'expo';
import * as firebase from 'firebase';
import {firebaseConfig} from "./ApiKeys";
export default class HomeScreen extends React.Component {
static navigationOptions = {
header: null,
};
onChooseImagePress = async () => {
let result = await ImagePicker.launchCameraAsync();
//let result = await ImagePicker.launchImageLibraryAsync();
if (!result.cancelled) {
this.uploadImage(result.uri, "test-image")
.then(() => {
Alert.alert("Success");
})
.catch((error) => {
Alert.alert(error);
});
}
}
uploadImage = async (uri, imageName) => {
const response = await fetch(uri);
const blob = await response.blob();
var ref = firebase.storage().ref().child("images/" + imageName);
return ref.put(blob);
}
render() {
return (
<View style={styles.container}>
<Button title="Choose image..." onPress={this.onChooseImagePress} />
</View>
);
}
}
const styles = StyleSheet.create({
container: { flex: 1, paddingTop: 50, alignItems: "center", },
});
}
Multiple syntactical issues in your code:
const styles... should be defined inside the render function currently its dangling outside the class
Brackets mismatch
return (
<View style={styles.container}>
<Button title="Choose image..." onPress={this.onChooseImagePress} />
</View>
);
}
} // the class ends here
Please let me know if it still doesn't work
Try to use below code
constructor() {
super();
this.state = { };
this.onChooseImagePress= this.onChooseImagePress.bind(this);
}
<Button title="Choose image..." onPress={() => this.onChooseImagePress()} />
I want my apps to display what the user entered in the tag under it with Redux.
So this is my container:
const mapStateToProps = state => ({
text: state
})
const mapDispatchToProps = (dispatch) => ({
addToList: () => { dispatch({ type: 'ADD_LIST' }) },
})
export default connect(mapStateToProps, mapDispatchToProps)(TodoList)
Here is my component:
class TodoList extends Component {
render() {
return (
<View>
<TextInput
style={{height: 40, width: 300}}
placeholder="Type here to translate!"
onChangeText={(text) => this.props.text}
/>
<Button
title="Submit"
onPress={this.props.addToList}/>
<View>
<Text>{this.props.text}</Text>
</View>
</View>
)
}
}
export default TodoList;
Here is the Store:
export const todoList = (state = [], action = {}) => {
switch (action.type) {
case 'ADD_LIST':
return [
...state,
action.todo
];
default:
return state;
}
}
let storeTodoList = createStore(todoList);
export default storeTodoList;
So i'm trying to get the text entered, add it to a list stored in the store and then display it, but i have absolutely no clue how to do this...
You have a few things going on here...
Your onChangeText listener isn't doing anything. You need to capture the text entered into the component and send it to your dispatcher.
You need to include the new text passed in as part of your action creator.
mapStateToProps is responsible for taking the elements in application state and mapping it to the props to be made available to your component. For this example, your application state is pretty simple. It is just { text: 'SOME TEXT' }.
You need to create a Provider for your Redux store. It should work at the root level of your app.
Here are all of the parts:
App.js (the application controller where the Provider is created)
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import todoList from './actions/Reducer';
import { createStore } from 'redux';
import Root from './Root';
class App extends Component {
store = createStore(todoList);
render() {
return (
<Provider store={this.store}>
<Root/>
</Provider>
)
}
}
export default App;
Root.js
import React, { Component } from 'react';
import { View, TextInput, Button, Text } from 'react-native';
import { connect } from 'react-redux';
class Root extends Component {
render() {
const displayText = this.props.textList.join();
return (
<View>
<TextInput
style={{height: 40, width: 300}}
placeholder="Type here to translate!"
onChangeText={(text) => this.props.updateField(text)}
/>
<Button
title="Submit"
onPress={() => this.props.addToList(this.props.text)}/>
<View>
<Text>{displayText}</Text>
</View>
</View>
)
}
}
const mapStateToProps = state => ({
text: state.textField,
textList: state.list
});
const mapDispatchToProps = (dispatch) => ({
updateField: (newText) => { dispatch({ type: 'FIELD_CHANGE', text: newText })},
addToList: (text) => { dispatch({ type: 'ADD_LIST', text: text }) },
});
export default connect(mapStateToProps, mapDispatchToProps)(Root)
Reducer.js (controls your state object)
const INITIAL_STATE = {
textField: '',
list: []
};
export default todoList = (state = INITIAL_STATE, action = {}) => {
switch (action.type) {
case 'FIELD_CHANGE':
return {
...state,
textField: action.text
};
case 'ADD_LIST':
return {
textField: '',
list: [...state.list, action.text]
};
default:
return state;
}
};
EDIT-Changed example to add to a list. Note: This is not the proper way to show a list of items in RN. I'm just throwing the string into a Text field for the example. Use FlatList to properly show a list of items.
I have my side menu via DrawerNavigator. I know that to customize the drawer, it's in "contentComponents" props.
I want for example, put a button who open a modal like : Share (to share the app on other social media)
But for now, all my button are route. So if I click on it, it's redirect to the page (normal). I just want to add a button who react and not redirect.
I don't know how to custom that in the Component dynamically. I think about hardcoded each button (some for redirect, some for display simple modal).
Here is my code :
index.android.js
const DrawerContent = (props) => (
<ScrollView>
<View style={styles.container}>
<Text style={styles.logo}>TechDico</Text>
<Text style={{ paddingLeft: 10, paddingRight: 10, fontSize: 13, textAlign: 'center', color: '#f4f4f4' }}>Des millions de traductions classées par domaine d'activité</Text>
</View>
<DrawerItems style={{ marginTop: 30 }} {...props} />
</ScrollView>
)
const appNavigator = DrawerNavigator({
Redirection1: {
screen: Index,
navigationOptions: {
drawerLabel: 'Redirection1',
drawerIcon: ({ tintColor }) => (<Icon name="home" size={20} color={tintColor} />),
}
},
DisplayModal: {
screen: Index,
navigationOptions: {
drawerLabel: 'DisplayModal',
drawerIcon: ({ tintColor }) => (<Icon name="home" size={20} color={tintColor} />),
}
},
Redirection2: {
screen: Index,
navigationOptions: {
drawerLabel: 'Redirection2',
drawerIcon: ({ tintColor }) => (<Icon name="home" size={20} color={tintColor} />),
}
}, }, {
// define customComponent here
contentComponent: DrawerContent,
contentOptions: {
inactiveTintColor: '#000000',
activeTintColor: '#1eacff',
showIcon: true,
}
});
Index class
export default class Index extends Component {
renderRoot = () => {
const { navigation } = this.props;
console.log("My Navigation ", navigation);
switch (navigation.state.key) {
case 'Redirection1':
return (
<App navigation={navigation} />
);
case 'DisplayModal':
// TODO I don't want to return so I can remove to cancel the redirection, but now, how can I display a modal without redirect.
return (
<DisplayModal navigation={navigation} />
);
case 'Redirection2':
return (
<Redirection2 navigation={navigation} />
);
default:
return (
<Test navigation={navigation} />
);
}
}
I'm using 'react-navigation'.
I'm looking at the same task as well. I think having multiple routes pointing to the same screen type may cause eventually a mess with state management, as each screen instance is different.
Looking at the source code in DrawerSidebar/DrawerNavigatorItems it seems all items in the sidebar list are those found in drawer's route config (unless we rewrite completely DrawerNavigatorItems). So maybe we may have a fake screen for some route and in componentWillMount implement required action and then navigate to the default route.
Here is a sample code:
let drawer = DrawerNavigator({
Main: {
screen: MainScreen,
},
About: {
screen: AboutScreen,
},
ContactUs: {
screen: ContactUsFakeScreen,
},
});
const mailUrl = "mailto:test#test.com";
class ContactUsFakeScreen extends React.Component {
componentWillMount() {
let self = this;
Linking.canOpenURL(mailUrl)
.then(self.openEmail)
.catch(err => self.openEmail(false));
}
openEmail(supported) {
if (supported) {
Linking.openURL(mailUrl).catch(err => {});
}
let { navigation } = this.props;
navigation.navigate('Main');
}
render() {
return null;
}
}
Here Main/MainScreen and About/AboutScreen are regular routes and screens, while ContactUs/ContactUsFakeScreen only pretend to be a route and a screen. Clicking on ContactUs will trigger componentWillMount which deals with email screen and then eventually navigates to the MainScreen (Main route).
Another approach could be to hijack getStateForAction from drawer router and put some extra routing logic there replacing destination route on the fly. Something along these lines:
const defaultDrawerGetStateForAction = drawer.router.getStateForAction;
drawer.router.getStateForAction = (action, state) => {
let newState = defaultDrawerGetStateForAction(action, state);
if (action.type === 'Navigation/NAVIGATE' && action.routeName === 'ContactUs') {
// extra logic here ...
newState.routes.forEach(r => {
if (r.key === 'DrawerClose') {
// switching route from ContactUs to Main.
r.index = 0;
}
});
}
return newState;
}
And if an item in the drawer list is not even actionable (like copyright), then fake screen will look even simpler (note styling via navigationOptions):
let drawer = DrawerNavigator({
...
Copyright: {
screen: Copyright,
},
});
class Copyright extends React.Component {
static navigationOptions = {
drawerLabel: ({ tintColor, focused }) =>
(<Text style={{color: '#999'}}>Copyright 2017</Text>)
)
};
componentWillMount() {
let { navigation } = this.props;
navigation.navigate('Main');
}
render() {
return null;
}
}
I am new in react-native and i want to open url in default browser like Chrome in Android and iPhone both.
We open url via intent in Android same like functionality i want to achieve.
I have search many times but it will give me the result of Deepklinking.
You should use Linking.
Example from the docs:
class OpenURLButton extends React.Component {
static propTypes = { url: React.PropTypes.string };
handleClick = () => {
Linking.canOpenURL(this.props.url).then(supported => {
if (supported) {
Linking.openURL(this.props.url);
} else {
console.log("Don't know how to open URI: " + this.props.url);
}
});
};
render() {
return (
<TouchableOpacity onPress={this.handleClick}>
{" "}
<View style={styles.button}>
{" "}<Text style={styles.text}>Open {this.props.url}</Text>{" "}
</View>
{" "}
</TouchableOpacity>
);
}
}
Here's an example you can try on Expo Snack:
import React, { Component } from 'react';
import { View, StyleSheet, Button, Linking } from 'react-native';
import { Constants } from 'expo';
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<Button title="Click me" onPress={ ()=>{ Linking.openURL('https://google.com')}} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
});
A simpler way which eliminates checking if the app can open the url.
loadInBrowser = () => {
Linking.openURL(this.state.url).catch(err => console.error("Couldn't load page", err));
};
Calling it with a button.
<Button title="Open in Browser" onPress={this.loadInBrowser} />
Try this:
import React, { useCallback } from "react";
import { Linking } from "react-native";
OpenWEB = () => {
Linking.openURL(url);
};
const App = () => {
return <View onPress={() => OpenWeb}>OPEN YOUR WEB</View>;
};
Hope this will solve your problem.
In React 16.8+, the following can be used to create an ExternalLinkBtn component for opening external links in the browser.
import React from 'react';
import { Button, Linking } from 'react-native';
const ExternalLinkBtn = (props) => {
return <Button
title={props.title}
onPress={() => {
Linking.openURL(props.url)
.catch(err => {
console.error("Failed opening page because: ", err)
alert('Failed to open page')
})}}
/>
}
Below is an example of using our ExternalLinkBtn component
export default function exampleUse() {
return (
<View>
<ExternalLinkBtn title="Example Link" url="https://example.com" />
</View>
)
}