I have a createSwitchNavigator which has 3 routes with initialRouteName as AuthLoading
App.js
import AuthLoadingScreen from './screens/AuthLoadingScreen';
import {
createDrawerNavigator,
createBottomTabNavigator,
DrawerItems,
createSwitchNavigator,
createStackNavigator
} from "react-navigation";
import AuthScreen from "./screens/AuthScreen";
import ProductsScreen from "./screens/ProductsScreen";
import AuthLoadingScreen from "./screens/AuthLoadingScreen";
import ForgetPasswordScreen from "./screens/ForgetPasswordScreen";
import ProductsDemo from "./screens/ProductsDemo";
import RegisterScreen from "./screens/RegisterScreen";
const AppNavigator = () => {
return <MainNavigation />;
};
const AppBottomTabNavigator = createBottomTabNavigator(
{
home: ProductsScreen
}
);
const MainNavigation = createDrawerNavigator(
{
Home: AppBottomTabNavigator,
},
{
drawerWidth: width - width / 4,
drawerPosition: "left",
drawerOpenRoute: "DrawerOpen",
drawerCloseRoute: "DrawerClose",
drawerToggleRoute: "DrawerToggle",
drawerBackgroundColor: "#fff",
initialRouteName: "Home"
}
);
const AuthDrawerNavigator = createDrawerNavigator(
{
Products: createStackNavigator(
{
SignIn: AuthScreen,
ForgetPwd: ForgetPasswordScreen,
SignUp: RegisterScreen,
Product: ProductsDemo
},
{
initialRouteName: "Product",
navigationOptions: {
header: null
}
}
)
},
{
drawerWidth: width - width / 4,
drawerPosition: "left"
}
);
class App extends Component {
componentDidMount() {
SplashScreen.hide();
navigatorRef = this.navigator;
}
render() {
const Main = createSwitchNavigator({
AuthLoading: AuthLoadingScreen,
Auth: AuthDrawerNavigator,
App: AppNavigator
});
return (
<StoreProvider store={store}>
<PaperProvider theme={theme}>
<Main />
</PaperProvider>
</StoreProvider>
);
}
}
export default App;
The AuthLoadingScreen just checks... if app has a token, log user in else route to AuthDrawerNavigator, the AuthDrawerNavigator has a registration and login page.
AuthLoadingScreen.js
import React, { Component } from "react";
import { View, ActivityIndicator, AsyncStorage, StatusBar } from
"react-native";
class AuthLoadingScreen extends Component {
constructor() {
super()
this.loadApp()
}
loadApp = async () => {
const userToken = await AsyncStorage.getItem('userToken');
this.props.navigation.navigate(userToken? 'App' : 'Auth');
}
render() {
return (
<View
style={{
justifyContent: "center",
alignItems: "center",
flex: 1,
backgroundColor: "#fff"
}}
>
<StatusBar translucent={false} backgroundColor="#007aff" />
<ActivityIndicator size="large" color="#007aff" />
</View>
);
}
}
export default AuthLoadingScreen;
In my RegisterScreen, as the user gets registered on backend, im storing his or her details locally using AsyncStorage...
const registerUser = async (first_name, last_name, email, pwd_1, phone, dispatch) => {
axios.post(`${ROOT_URL}registerUser`, {
phone: `+91${phone}`,
first_name: `${first_name}`,
last_name: `${last_name}`,
email: `${email}`,
pwd: `${pwd_1}`
})
.catch(err => dispatch({
type: REQ_FAIL,
val: err.message
}))
dispatch({
type: REQ_SUCCESS
})
await AsyncStorage.setItem('f_name', first_name);
await AsyncStorage.setItem('l_name', last_name);
await AsyncStorage.setItem('email', email);
await AsyncStorage.setItem('phone', phone);
}
Now, as the user is getting registered, im storing their details locally and after registration im routed to loginScreen and after login, im able to see details in homeScreen
ProductsScreen.js
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
StatusBar,
Image,
TextInput,
AsyncStorage,
} from "react-native";
import { Appbar } from "react-native-paper";
import { Icon } from "react-native-elements";
class ProductsScreen extends Component {
state = {
first_name: "",
last_name: "",
phone: "",
email: ""
};
async componentWillMount() {
let f_name = await AsyncStorage.getItem("f_name");
let l_name = await AsyncStorage.getItem("l_name");
let phone = await AsyncStorage.getItem("phone");
let email = await AsyncStorage.getItem("email");
this.setState({
first_name: f_name,
last_name: l_name,
phone: phone,
email: email
});
}
render() {
const { navigation } = this.props;
return (
<View style={{ flex: 1, backgroundColor: "#fff" }}>
<View style={{ flex: 1 }}>
<View
style={{
backgroundColor: "#fff",
borderRadius: 12,
flexDirection: "row",
justifyContent: "space-around",
alignItems: "center",
marginHorizontal: 12,
marginVertical: 5,
elevation: 1,
zIndex: 9
}}
>
<Image
style={{
height: 50,
width: 50,
marginVertical: 8
}}
alignSelf="center"
resizeMode="contain"
source={require("../components/Images/avatar.png")}
/>
<View style={{ justifyContent: "space-around" }}>
<Text>{`${this.state.first_name} ${
this.state.last_name
}`}</Text>
<Text>{this.state.phone}</Text>
<Text>{this.state.email}</Text>
</View>
</View>
</View>
</View>
);
}
}
export default ProductsScreen;
But next time as the user is already registered,user is directly routed to loginScreen and after logging in, this time AsyncStorage is returning null
Though the code for retrieving values from asyncStorage is same as above:
async componentWillMount() {
let f_name = await AsyncStorage.getItem("f_name");
let l_name = await AsyncStorage.getItem("l_name");
let phone = await AsyncStorage.getItem("phone");
let email = await AsyncStorage.getItem("email");
this.setState({
first_name: f_name,
last_name: l_name,
phone: phone,
email: email
});
}
Related
I finished a React Native course, and I'm trying to make a chat app to practice.
Summarize the problem:
I have 2 screens ,ContactList.js and ChatRoom.js
I have a Navigation stack with these two screens Navigation.js
The Navigation component is imported and rendered in App.js
I added FCM module to handle notifications
The goal is to execute the function that loads messages in the chatroom _loadMessages(), when the app receives a notification on foreground state. And to execute the function (I didn't create it yet) to update unread message in a global state.
What I've tried
I followed react native firebase docs, I have a function that handle notification on foreground declared inside App.js. The problem is that I can't tell the other components (the screens) to execute their functions. The "Ref" method can't be used cause I'm not calling the child component (the screens) directly inside the App.js, I'm calling and rendering the Navigation.js Stack instead.
So, in this case, when we have a navigation component called on app.js, how can we tell other components to execute a function that is declared inside them?
App.js
import React, { useEffect } from 'react'
import Navigation from './Navigation/Navigation'
import messaging from '#react-native-firebase/messaging';
export default function App() {
requestUserPermission = async () => {
//On récupere le token
const token = await messaging().getToken();
console.log('TOKEN: ' + token)
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Authorization status:', authStatus);
}
}
handleForegroundNotification = () => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
console.log('A new FCM message arrived!', JSON.stringify(remoteMessage));
});
return unsubscribe;
}
useEffect(() => {
this.requestUserPermission();
this.handleForegroundNotification();
}, []);
return (
<Navigation />
)
}
Navigation.js
import { createAppContainer } from "react-navigation"
import { createStackNavigator } from "react-navigation-stack"
import ContactList from '../Components/ContactList'
import ChatRoom from '../Components/ChatRoom'
const ChatStackNavigator = createStackNavigator({
ContactList: {
screen: ContactList,
navigationOptions: {
title: 'Contacts'
}
},
ChatRoom: {
screen: ChatRoom,
navigationOptions: {
title: 'Conversation'
}
}
})
export default createAppContainer(ChatStackNavigator)
ChatRoom.js
import React from 'react'
import { View, StyleSheet, Text, Image, SafeAreaView, TextInput, Alert, FlatList, ActivityIndicator } from 'react-native'
import { sendMessage } from '../API/sendMessageApi'
import { getMessages } from '../API/getMessagesApi'
import MessageItem from './MessageItem'
class ChatRoom extends React.Component {
constructor(props) {
super(props)
this.message = ""
this.contact = this.props.navigation.getParam('contact')
this.state = {
defautInputValue: "",
listMessages: [],
isLoading: true
}
}
_textInputChanged(text) {
this.message = text
}
_sendMessage() {
this.setState({ defautInputValue: " " });
sendMessage('1', this.contact.id_contact, this.message).then(() => {
this._loadMessages();
});
}
_loadMessages() {
getMessages('1', this.contact.id_contact).then((data) => {
this.setState({ listMessages: data, isLoading: false, defautInputValue: "" })
});
}
componentDidMount() {
this._loadMessages();
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={[styles.loading_container]}>
<ActivityIndicator size="large" color="orange" />
</View>
)
}
}
render() {
//console.log('Contact ID: ' + JSON.parse(this.contact))
return (
<SafeAreaView style={styles.container}>
<View style={styles.contact}>
<View style={styles.image_container}>
<Image
style={styles.image}
source={{ uri: 'https://moonchat.imedramdani.com/avatar/' + this.contact.avatar }}
></Image>
</View>
<View style={styles.username_container}>
<Text style={styles.username}>{this.contact.username}</Text>
</View>
</View>
{/* BODY */}
<View style={styles.body}>
<FlatList
ref={ref => this.flatList = ref}
onContentSizeChange={() => this.flatList.scrollToEnd({ animated: true })}
data={this.state.listMessages}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) =>
<MessageItem
message={item}
/>}
>
</FlatList>
</View>
<View style={styles.input_container}>
<TextInput
style={styles.input}
onChangeText={(text) => this._textInputChanged(text)}
onSubmitEditing={() => this._sendMessage()}
defaultValue={this.state.defautInputValue}
placeholder="Aa"
></TextInput>
</View>
{this._displayLoading()}
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#1d2733',
},
contact: {
height: 50,
backgroundColor: '#1d2733',
marginTop: 0,
flexDirection: 'row',
borderBottomColor: 'grey',
borderWidth: 1
},
username_container: {
//backgroundColor: 'red',
flex: 3,
justifyContent: 'center',
alignItems: 'flex-start'
},
username: {
fontSize: 20,
color: 'white'
},
image_container: {
//backgroundColor: 'blue',
flex: 1,
justifyContent: 'center'
},
image: {
//backgroundColor: 'yellow',
height: 45,
width: 45,
marginLeft: 10,
borderRadius: 25
},
body: {
//backgroundColor: 'red',
flex: 1
},
input_container: {
height: 75,
//backgroundColor:'blue',
padding: 5
},
input: {
paddingLeft: 20,
height: 50,
backgroundColor: 'white',
borderWidth: 1,
borderRadius: 25,
borderColor: '#D5D5D5',
fontSize: 20
},
loading_container: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
});
export default ChatRoom
Thanks!
I set up a solution that worked, but I don't know if it is a proper way.
I restored App.js
import React from 'react'
import Root from './Root'
import { Provider } from 'react-redux'
import Store from './Store/configureStore'
class App extends React.Component {
render() {
return (
<Provider store={Store}>
<Root />
</Provider>
)
}
}
export default App
I created a now component Root.js which contains notification handler
import React from 'react'
import Navigation from './Navigation/Navigation'
import messaging from '#react-native-firebase/messaging'
import { connect } from 'react-redux'
class Root extends React.Component {
requestUserPermission = async () => {
//On récupere le token
const token = await messaging().getToken();
console.log('TOKEN: ' + token)
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Authorization status:', authStatus);
}
}
handleForegroundNotification = () => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
console.log('A new FCM message arrived!', JSON.stringify(remoteMessage));
const action = { type: "RELOAD_MESSAGES", value: '1' }
this.props.dispatch(action)
});
return unsubscribe;
}
componentDidMount() {
this.requestUserPermission();
this.handleForegroundNotification();
}
render() {
return (
<Navigation />
)
}
}
const mapStateToProps = (state) => {
return {
loadyourself: state.loadyourself
}
}
export default connect(mapStateToProps)(Root)
The store is provided in App.js to let Root.js access the global state.
When a notification is received at foreground, the Root.js update a key in the global state named "loadyourself". When the state is updated, the ChatRoom.js which is connected to the store too, trigger the componentDidUpdate()
componentDidUpdate() {
if (this.props.loadyourself == "1") {
this._reloadMessages();
}
}
of course, to avoid the infinite loop, the _reloadMessages() restore default value in the global state of loadyourself key
_reloadMessages() {
const action = { type: "RELOAD_MESSAGES", value: '0' }
this.props.dispatch(action)
getMessages('1', this.contact.id_contact).then((data) => {
this.setState({ listMessages: data })
});
}
The messages are updated, the global state is re-initialized, the componentDidUpdate() does not trigger until next notification.
It works. Like I said, I don't know if there is a more proper way, I'm new in React-Native (2 weeks). I am open to other solutions
I am searching for a couple of days and I can't find out why redux is not updating the state I don't see any problem in my code please help me to find the problem.
it is a simple login project. I can see that data changes inside the reducer when debugging but it's not being mapped to props and state is not changing.
this is my code:
actions.js
import {
LOGIN_SUCCESS,
LOGIN_FAILURE,
} from './types'
export const loginSuccess = (user) => ({
type: LOGIN_SUCCESS,
payload: user
})
export const loginFailure = (error) => ({
type: LOGIN_FAILURE,
payload: error
})
loginReducer.js
import {
LOGIN_SUCCESS,
LOGIN_FAILURE,
} from './types'
const initialState = {
user: null,
errorMessage: null
}
export const loginReducer = (state = initialState, action) => {
switch (action.type) {
case LOGIN_SUCCESS:
return {...state,user: action.payload, errorMessage: null }
case LOGIN_FAILURE:
return {...state,errorMessage: action.payload }
default:
return state;
}
}
loginScreen.js
import React from 'react'
import {
Text,
View,
ImageBackground,
Button,
StyleSheet
} from 'react-native'
import { TextField } from 'react-native-material-textfield';
import { loginSuccess, loginFailure } from './reduxx/actions'
import { connect } from "react-redux";
class LoginScreen extends React.Component {
constructor(props) {
super(props)
this.state = {
email: "",
password: "",
}
}
_handlePress() {
this.props.login(this.state.email, this.state.password)
// user in propa is undifined
this.props.user
}
render() {
let { email, password } = this.state
return (
<ImageBackground source={require('./images/loginBackground.jpg')} style={styles.imageBackground}>
<View style={styles.mainContainer}>
<View style={styles.box}>
<TextField label="Email" value={email} onChangeText={email => this.setState({ email })} />
<TextField label="Password" textContentType="password" value={password} onChangeText={password => this.setState({ password })} />
<Button onPress={() => {
this._handlePress()
}} color="green" title="Sign in" style={styles.button} />
</View>
<View style={styles.bottomTextContainer}>
<Text style={{ color: "white" }}>Don't have an account?</Text>
<Text style={{ color: "lightgreen" }} onPress={() => this.props.navigation.navigate("Register")}> Sign up</Text>
</View>
</View>
</ImageBackground>
)
}
}
function mapStateToProps(state) {
return {
user: state.user,
errorMessage: state.errorMessage
}
}
function mapDispatchToProps(dispatch) {
return {
login: (email, password) => {
try {
let user = //get user from database
dispatch(loginSuccess(user))
} catch (error) {
dispatch(loginFailure(error))
}
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
const styles = StyleSheet.create({
imageBackground: {
width: '100%',
height: '100%'
},
mainContainer: {
flex: 1,
justifyContent: "center"
},
box: {
backgroundColor: 'white',
padding: 8,
margin: 8
},
button: {
margin: 10
},
bottomTextContainer: {
position: "absolute",
bottom: 8,
alignSelf: "center",
flexDirection: "row"
}
});
app.js
import React from 'react';
import { Provider } from "react-redux"
import store from "./reduxx/store";
import AppContainer from './Navigator'
export default class App extends React.Component {
render() {
return (
<Provider store={store} >
<AppContainer />
</Provider>
);
}
}
store.js
import {createStore} from "redux";
import { combineReducers } from 'redux';
import {loginReducer} from './loginReducer'
const rootReducer = combineReducers({
loginReducer,
});
export default store = createStore(rootReducer)
The state which comes into mapStateToProps contains all the combined reducers, so we need to access the reducer name first from the state (like state.loginReducer.user), before trying to access the data of that reducer.
PFB code which should work:
function mapStateToProps(state) {
return {
user: state.loginReducer.user,
errorMessage: state.loginReducer.errorMessage
}
}
Change here :
function mapStateToProps(state) {
return {
user: state.user,
errorMessage: state.errorMessage
}
}
TO
function mapStateToProps(state) {
return {
login: state.loginReducer
}
}
And then this.props.user to this.props.login.user and this.props.errorMessage to this.props.login.errorMessage in all the occurrence.
I'm currently developing an app using react native, right now my issue is that i couldn't navigate to main screen after login. Below is my code.
This is App.js (EDITED)
import React from 'react';
import { Loading } from './components/common/';
import TabNavigator from './screens/TabNavigator';
import AuthNavigator from './screens/AuthNavigator';
import MainNavigator from './screens/MainNavigator';
import deviceStorage from './services/deviceStorage.js';
import { View, StyleSheet } from 'react-native';
export default class App extends React.Component {
constructor() {
super();
this.state = {
token: '',
loading: true
}
this.newJWT = this.newJWT.bind(this);
this.deleteJWT = deviceStorage.deleteJWT.bind(this);
this.loadJWT = deviceStorage.loadJWT.bind(this);
this.loadJWT();
}
state = {
isLoadingComplete: false,
};
newJWT(token){
this.setState({
token: token
});
}
render() {
if (this.state.loading) {
return (
<Loading size={'large'} />
);
} else if (!this.state.token) {
return (
<View style={styles.container}>
<AuthNavigator screenProps = {{setToken:this.newJWT}} />
</View>
);
} else if (this.state.token) {
return (
<View style={styles.container}>
<MainNavigator screenProps = {{token: this.state.token,
deleteJWT:this.deleteJWT,}} />
</View>
);
}
}
}
This is Login.js (EDITED-v2)
import React, { Component, Fragment } from 'react';
import { Text, View, StyleSheet, ImageBackground, KeyboardAvoidingView,
TouchableOpacity, TextInput, Alert } from 'react-native';
import axios from 'axios';
import deviceStorage from '../services/deviceStorage';
class Login extends Component {
constructor(props) {
super(props)
this.state = {
username: '',
password: '',
error: '',
loading: false
};
this.loginUser = this.loginUser.bind(this);
this.onLoginFail = this.onLoginFail.bind(this);
}
loginUser() {
const { username, password, password_confirmation } = this.state;
this.setState({ error: '', loading: true });
// NOTE Post to HTTPS only in production
axios.post("http://192.168.1.201:8000/api/login",{
username: username,
password: password
})
.then((response) => {
console.log('response',response)
deviceStorage.saveKey("token", response.data.token);
console.log(response.data.token);
this.props.newJWT(response.data.token);
})
.catch((error) => {
const status = error.response.status
if (status === 401) {
this.setState({ error: 'username or password not recognised.' });
}
this.onLoginFail();
//console.log(error);
//this.onLoginFail();
});
}
onLoginFail() {
this.setState({
error: 'Login Failed',
loading: false
});
}
render() {
// other codes here
}
const styles = StyleSheet.create({
// other codes here
});
export { Login };
This is TabNavigator.js (Added)
import React from 'react';
import { Text } from 'react-native';
import { createMaterialTopTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import Profile from '../screens/Profile';
const TabNavigator = createMaterialTopTabNavigator(
{
Profile: {
screen: props => <Profile {...props.screenProps} />,
navigationOptions: {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-person' : 'ios-person'} //TODO change to focused
icon
size={30}
style={{ color: tintColor }}
/>
),
}
},
},
{ initialRouteName: 'Profile',
tabBarPosition: 'top',
swipeEnabled: false,
animationEnabled: true,
lazy: true,
tabBarOptions: {
showLabel: false,
showIcon: true,
activeTintColor: 'orange',
inactiveTintColor: 'orange',
style: {
backgroundColor: '#555',
},
indicatorStyle: {
color: '#orange'
}
}
}
);
const screenTitles = {
Profile: { title: 'Profiler' },
Home: { title: 'Home' },
};
TabNavigator.navigationOptions = ({ navigation }) => {
const { routeName } = navigation.state.routes[navigation.state.index];
const headerTitle = screenTitles[routeName].title;
const tabBarVisible = false;
return {
headerTitle,
tabBarVisible
};
};
export default TabNavigator;
This is my AuthLoadingScreen.js
import React from 'react';
import { View } from 'react-native';
import { Login } from '../screens/Login';
class AuthLoadingScreen extends React.Component {
constructor(props){
super(props);
this.state = {
showLogin: true
};
this.whichForm = this.whichForm.bind(this);
this.authSwitch = this.authSwitch.bind(this);
}
authSwitch() {
this.setState({
showLogin: !this.state.showLogin
});
}
whichForm() {
if(this.state.showLogin){
return(
<Login newJWT={this.props.newJWT} authSwitch={this.authSwitch} />
);
} else {
}
}
render() {
return(
<View style={styles.container}>
{this.whichForm()}
</View>
);
}
}
export default AuthLoadingScreen;
const styles = {
// style codes here
};
Lastly, this is my Profile.js
import React, { Component } from 'react';
import { View, Text, TouchableOpacity, Alert, Platform } from
'react-native';
import { Button, Loading } from '../components/common/';
import axios from 'axios';
export default class Profile extends Component {
constructor(props){
super(props);
this.state = {
loading: true,
email: '',
name: '',
error: ''
}
}
componentDidMount(){
this.onLocationPressed();
const headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.props.token
};
axios({
method: 'GET',
url: 'http://192.168.1.201:8000/api/user',
headers: headers,
}).then((response) => {
console.log('response',response)
console.log('response2',this.props.token)
this.setState({
email: response.data.user.email,
name: response.data.user.name,
loading: false
});
}).catch((error) => {
console.log(error);
this.setState({
error: 'Error retrieving data',
loading: false
});
});
}
render() {
const { container, emailText, errorText } = styles;
const { loading, email, name, error } = this.state;
if (loading){
return(
<View style={container}>
<Loading size={'large'} />
</View>
)
} else {
return(
<View style={container}>
<View>
<Text style={emailText}>Your email: {email}</Text>
<Text style={emailText}>Your name: {name}</Text>
</View>
<Button onPress={this.props.deleteJWT}>
Log Out
</Button>
</View>
);
}
}
}
const styles = {
// style codes here
};
I've fixed the previous problem that couldn't start the app. Right now i can see the login screen, but when i pressed login, there's a yellow box that indicates some problem. I've included the screenshot below.
Lastly i've added the deviceStorage.js
deviceStorage.js
import { AsyncStorage } from 'react-native';
const deviceStorage = {
async saveKey(key, valueToSave) {
try {
await AsyncStorage.setItem(key, valueToSave);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
},
async loadJWT() {
try {
const value = await AsyncStorage.getItem('token');
if (value !== null) {
this.setState({
token: value,
loading: false
});
} else {
this.setState({
loading: false
});
}
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
},
async deleteJWT() {
try{
await AsyncStorage.removeItem('token')
.then(
() => {
this.setState({
token: ''
})
}
);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
}
};
export default deviceStorage;
Before navigate
After navigate
This is my setup. It works like a charm. Sorry if it's a bit messy. I removed some stuff for clarity and I may have missed something:
App.js
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { Asset, Font, Icon } from 'expo';
import { ENDPOINT, USER_TYPE } from './src/config'
import { Loading } from './src/components/common/';
import deviceStorage from './src/services/deviceStorage.js';
import TabNavigator from './src/TabNavigator';
import AuthNavigator from './src/AuthNavigator';
import MainNavigator from './src/MainNavigator';
import globalStyles from './src/globalStyles';
import './ReactotronConfig';
export default class App extends React.Component {
constructor() {
super();
this.state = {
jwt: '',
loading: true,
};
this.newJWT = this.newJWT.bind(this);
this.deleteJWT = deviceStorage.deleteJWT.bind(this);
this.loadJWT = deviceStorage.loadJWT.bind(this);
this.loadJWT();
}
state = {
isLoadingComplete: false,
};
newJWT(jwt) {
this.setState({
jwt: jwt
});
}
render() {
if (this.state.loading) {
return (
<Loading size={'large'} />
);
} else if (!this.state.jwt) {
//console.log(this.props, '<=== app.js');
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AuthNavigator screenProps={{setToken: this.newJWT }} />
</View>
);
} else {
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<MainNavigator
screenProps={{ jwt: this.state.jwt,
deleteToken: this.deleteJWT,
}}
/>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
justifyContent: 'center',
},
});
AuthNavigator.js
import React from 'react';
import { createAppContainer, createBottomTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import AuthScreen from './screens/AuthScreen';
const AuthNavigator = createBottomTabNavigator(
{
Auth: (props) => {
return <AuthScreen {...props.screenProps} />;
}
},
{ initialRouteName: 'Auth',
tabBarOptions: {
showLabel: false,
activeBackgroundColor: '#eee',
}
}
);
export default createAppContainer(AuthNavigator);
MainNavigator.js
import React from 'react';
import { createStackNavigator, createAppContainer } from 'react-navigation';
import TabNavigator from './TabNavigator';
const MainNavigator = createStackNavigator({
Main: TabNavigator },
{
initialRouteName: 'Main',
defaultNavigationOptions: {
headerTitleStyle: {
fontSize: 20,
textTransform: 'uppercase'
}
}
});
export default createAppContainer(MainNavigator);
TabNavigator.js
import React from 'react';
import { Text } from 'react-native';
import { createMaterialTopTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import IconBadge from 'react-native-icon-badge';
import ProfileScreen from './screens/ProfileScreen';
import NotificationsScreen from './screens/NotificationsScreen';
import HomeStackNavigator from './HomeStackNavigator';
import CartStackNavigator from './CartStackNavigator';
import QuotesStackNavigator from './QuotesStackNavigator';
import InitialRoute from './InitialRoute';
const TabNavigator = createMaterialTopTabNavigator(
{
Profile: {
screen: props => <ProfileScreen {...props.screenProps} />,
navigationOptions: {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-person' : 'ios-person'} //TODO change to focused icon
size={30}
style={{ color: tintColor }}
/>
),
}
},
Home: HomeStackNavigator,
Quotes: QuotesStackNavigator,
Notifications: { screen: props => <NotificationsScreen {...props.screenProps} />,
navigationOptions: ({ screenProps }) => ({
tabBarIcon: ({ tintColor, focused }) => (
<IconBadge
MainElement={
<Ionicons
name={focused ? 'ios-notifications' : 'ios-notifications'}
size={30}
style={{ color: tintColor }}
/>
}
BadgeElement={
<Text style={{ color: '#FFFFFF' }}>{screenProps.unreadMessagesCount}</Text>
}
IconBadgeStyle={{ width: 15,
height: 15,
position: 'absolute',
top: 1,
left: -6,
marginLeft: 15,
backgroundColor: 'red' }}
Hidden={screenProps.unreadMessagesCount === 0}
/>
)
})
},
Cart: CartStackNavigator,
},
{ initialRouteName: 'Profile',
tabBarPosition: 'top',
swipeEnabled: false,
animationEnabled: true,
lazy: true,
tabBarOptions: {
showLabel: false,
showIcon: true,
activeTintColor: 'orange',
inactiveTintColor: 'orange',
style: {
backgroundColor: '#555',
},
indicatorStyle: {
color: '#orange'
}
}
}
);
const screenTitles = {
Profile: { title: 'Hola Maestro' },
Home: { title: 'Selecciona la Categoría' },
Quotes: { title: 'Mi Historial de Cotizaciones' },
Notifications: { title: 'Notificaciones' },
Cart: { title: 'Mi Pedido' },
};
TabNavigator.navigationOptions = ({ navigation }) => {
const { routeName } = navigation.state.routes[navigation.state.index];
const headerTitle = screenTitles[routeName].title;
const tabBarVisible = false;
return {
headerTitle,
tabBarVisible
};
};
export default TabNavigator;
Login.js
import React, { Component, Fragment } from 'react';
import { Text, View, StyleSheet, ImageBackground, KeyboardAvoidingView } from 'react-native';
import axios from 'axios';
import Ionicons from 'react-native-vector-icons/Ionicons';
//import Pusher from 'pusher-js/react-native';
import { ENDPOINT, USER_TYPE } from '../config'
import deviceStorage from '../services/deviceStorage';
import { Input, TextLink, Loading, Button } from './common';
import Header from '../components/Header';
class Login extends Component {
constructor(props){
super(props);
this.state = {
username: '',
password: '',
error: '',
loading: false
};
this.pusher = null; // variable for storing the Pusher reference
this.my_channel = null; // variable for storing the channel assigned to this user
this.loginUser = this.loginUser.bind(this);
}
loginUser() {
const { username, password, password_confirmation } = this.state;
axios.post(`${ENDPOINT}/login`, {
user: {
login: username,
password: password
}
})
.then((response) => {
deviceStorage.saveKey("id_token", response.headers.authorization);
this.props.newJWT(response.headers.authorization);
//this.setPusherData();
})
.catch((error) => {
this.onLoginFail();
});
}
onLoginFail() {
this.setState({
error: 'Usuario o contraseña incorrecta',
loading: false
});
}
}
render() {
const { username, password, error, loading } = this.state;
const { container, form, section, errorTextStyle, iconContainer, inputContainer, titleText } = styles;
return (
<View style={container}>
<Header title="¡Bienvenido Amigo Maestro!" />
<View style={form}>
<ImageBackground source={require('./cemento-login.jpg')} style={{ flex: 1, marginBottom: 30 }}>
<View style={{marginTop: 120}}>
<Text style={titleText}>INICIAR SESIÓN</Text>
<View style={section}>
<View style={iconContainer}>
<Ionicons
name={'ios-person'}
size={26}
style={{ color: '#fff', alignSelf: 'center' }}
/>
</View>
<View style={inputContainer}>
<Input
placeholder="Usuario"
value={username}
onChangeText={username => this.setState({ username })}
/>
</View>
</View>
<View style={section}>
<View style={iconContainer}>
<Ionicons
name={'ios-lock'}
size={26}
style={{ color: '#fff', alignSelf: 'center' }}
/>
</View>
<View style={inputContainer}>
<Input
secureTextEntry
placeholder="Contraseña"
value={password}
onChangeText={password => this.setState({ password })}
/>
</View>
</View>
</View>
</ImageBackground>
</View>
<KeyboardAvoidingView
behavior="padding"
keyboardVerticalOffset={30}
>
<TextLink style={{ }} onPress={this.props.formSwitch}>
Aún no estas registrado? Regístrate
</TextLink>
<TextLink style={{ }} onPress={this.props.forgotPassword}>
Olvidaste tu contraseña?
</TextLink>
<Text style={errorTextStyle}>
{error}
</Text>
{!loading ?
<Button onPress={this.loginUser}>
Ingresar
</Button>
:
<Loading size={'large'} />}
</KeyboardAvoidingView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
form: {
flex: 0.8
},
section: {
flexDirection: 'row',
backgroundColor: '#eee',
borderRadius: 3,
marginTop: 10,
height: 40,
marginLeft: '10%',
marginRight: '10%',
},
titleText: {
color: '#fff',
alignSelf: 'center',
fontSize: 20,
marginBottom: 10
},
errorTextStyle: {
alignSelf: 'center',
fontSize: 18,
color: 'red'
},
iconContainer: {
flex: 0.1,
height: 40,
borderRadius: 3,
alignSelf: 'center',
justifyContent: 'center',
backgroundColor: 'orange',
},
inputContainer: {
flex: 0.8,
alignSelf: 'flex-start',
marginLeft: -70,
}
});
export { Login };
Inside App.js you never change loading to false so the render method never gets to any of the other conditions. Once you have received the token you need to update state and change loading.
I need to change the content of my DrawerNavigator once the user is logged.
Can someone help me, please?
You can make your custom DrawerNavigator and change its View or Content dynamically. Here is an example:
public static Routes = DrawerNavigator({
Main: {
screen: StackNavigator({
Home: { screen: Home },
Contents: { screen: Contents },
ContentList: { screen: ContentList },
}, stackConfig('Home'))
},
MessageInbox: { screen: MessageInbox },
UserInfo: { screen: UserInfo}
}, {
initialRouteName: 'Main',
drawerWidth: 300,
drawerPosition: Platform.OS == 'ios' ? 'left' : 'right',
contentComponent: (props: any) => (
<DrawerComponent properties={props} />
)
}
)
DrawerComponent.js:
import React from 'react'
import { View, Platform, Text, Image, ScrollView, TouchableOpacity, AsyncStorage } from 'react-native'
import { DrawerItems, NavigationActions } from 'react-navigation'
import Icon from 'react-native-vector-icons/Ionicons';
export default class DrawerComponent extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
user: null,
avatarPic: 'avatars/0-1.png',
}
}
componentDidMount() {
this.fetchData()
}
fetchData = async () => {
let data = await AsyncStorage.getItem('UserData')
this.setState({ user: data })
}
render() {
let { user } = this.state
return (
<ScrollView contentContainerStyle={{ flex: 1 }}>
<View style={{ width: '100%', height: '30%', backgroundColor: 'white' }}>
<Image
style={{ width: 50, height: 50 }}
source={{ uri: this.state.avatarPic }}
/>
<TouchableOpacity
onPress={() => {
this.props.properties.navigation.navigate('UserInfo')
}}
style={{ marginTop: 10 }}>
<Text>{user.name ? user.name : 'New User'}</Text>
</TouchableOpacity>
</View>
<DrawerItems {...this.props.properties} />
</ScrollView>
)
}
}
I hope it help you.
I am new to react-native redux , i am updating my old code which i build by using flux pattern in Redux architecture , i am learning the usage of store , thunk , reducers and Actions , Here is some of my classes which i updated :-
HomeScreenClass :-
import React, { Component } from "react";
import {
StyleSheet,
View,
StatusBar,
ActivityIndicator,
Modal,
Platform,
Image,
ScrollView,
TouchableOpacity
} from "react-native";
import { Card } from "native-base";
import NavigationDrawer from "../../component/navigationDrawerComponent/NavigationDrawer";
import CategoryProductList from "../HomeScreen/screens/CategoryProducts";
import CustomText from "../../component/customComponent/CustomText";
import ProductScreen from "./screens/ProductScreen";
import ProductDetailScreen from "./screens/ProductDetailScreen";
import PopUpMenu from "../../component/navigationDrawerComponent/PopUpMenu";
import { Font } from "expo";
import LoginScreen from "../AuthScreen/LoginScreen";
import SignUp from "../AuthScreen/SignUpScreen";
import WebApi from "../../component/webServiceComponent/WebServiceHandler";
import ForgotPassword from "../AuthScreen/ForgotPassword";
import SignUpScreen from '../AuthScreen/SignUpScreen';
import ProfileScreen from "./screens/ProfileScreen";
import ChangePassword from "../AuthScreen/ChangePassword";
import EditProfileScreen from "./screens/EditProfileScreen";
import HtmlView from "./screens/HtmlView";
import OfflineNotice from "../../component/internetCheckComponent/OfflineNotice";
import { createRouter, NavigationProvider } from "#expo/ex-navigation";
import metrics from "../../component/displaySizeComponent/metrics";
import { connect } from 'react-redux';
import { HitAllApis} from '../../actions/ApiCallActions';
var self;
export const Router = createRouter(() => ({
about: () => AboutScreen,
products: () => ProductScreen,
aboutUs: () => AboutUs,
terms: () => Terms,
rateUs: () => RateUs,
productDetails: () => ProductDetailScreen,
ProductListing: () => CategoryProductList,
feedback: () => Feedback,
htmlView: () => HtmlView,
loginScreen: () => LoginScreen,
signUpScreen: () => SignUpScreen,
profileScreen: () => ProfileScreen,
editProfileScreen: () => EditProfileScreen,
forgotPasswordScreen: () => ForgotPassword,
changePassword: () => ChangePassword
}));
class HomeScreen extends Component {
constructor(props) {
super(props);
this.state={
modalVisible: false,
loaded: false
}
self = this;
}
componentWillMount() {
console.disableYellowBox = true;
self._loadFontsAsync();
const {dispatch} = this.props;
dispatch(HitAllApis());
}
componentDidMount() {
console.log("component*****" , this.props);
}
closeModal() {
this.setState({ modalVisible: false });
}
openModal() {
if (this.state.modalVisible) {
this.setState({ modalVisible: false });
} else {
this.setState({ modalVisible: true });
}
}
_loadFontsAsync = async () => {
await Font.loadAsync({
futuraLigtBt: require("../../fonts/futuraLightBt.ttf")
});
this.setState({ loaded: true });
};
render() {
console.log("under Render ", this.props)
if (!this.props.showData || !this.state.loaded) {
return (
<View style={{ flex: 1 }}>
<Image
style={{
height: metrics.DEVICE_HEIGHT + 24,
width: metrics.DEVICE_WIDTH
}}
source={require("../../assets/splash.png")}
/>
<ActivityIndicator
color="white"
style={styles.activityIndicator}
size="small"
animating={this.props.isLoading}
/>
<OfflineNotice />
</View>
);
}
return (
<View style={styles.container}>
<NavigationProvider router={Router}>
<StatusBar barStyle="default" hidden={false} />
<NavigationDrawer
openMenu={() => this.openModal()}
disableBack={true}
magentoData={this.props.magentoData}
bannerData={this.props.bannerData}
categoryList={this.props.categoryList}
/>
</NavigationProvider>
<Modal
transparent={true}
visible={this.state.modalVisible}
animationType={"none"}
onRequestClose={() => this.closeModal()}
>
<View style={styles.modalContainer}>
<View style={styles.modalInnerContainer}>
<TouchableOpacity
style={styles.navBar}
onPress={() => this.closeModal()}
/>
<View style={{ flex: 1, backgroundColor: "white" }}>
<Card>
<ScrollView showsVerticalScrollIndicator={false}>
<View style={styles.scrollView}>
<PopUpMenu
popUpList={this.state.popUpPageData}
closePopUp={() => this.closeModal()}
/>
</View>
</ScrollView>
</Card>
</View>
</View>
<TouchableOpacity
style={{ flex: 0.5, color: "transparent" }}
onPress={() => this.closeModal()}
/>
</View>
</Modal>
<OfflineNotice />
</View>
);
}
}
function mapStateToProps(state) {
//const { magentoData: [],showData,isLoading,popUpPageData: [],categoryList: [],bannerData: [],loaded,modalVisible} = state
return {
state
}
}
export default connect(mapStateToProps)(HomeScreen)
const styles = StyleSheet.create({
modalInnerContainer: {
flex: 0.5,
justifyContent: "center",
backgroundColor: "transparent",
flexDirection: "column"
},
container: { flex: 1, justifyContent: "center", alignItems: "center" },
activityIndicator: { position: "absolute", bottom: 20, alignSelf: "center" },
navBar: {
...Platform.select({
ios: {
height: 63
},
android: {
height: 55
}
}),
color: "transparent"
},
container: {
flex: 1,
backgroundColor: "white",
alignItems: "center",
justifyContent: "center"
},
scrollView: {
flex: 1,
borderRadius: 3,
justifyContent: "flex-start",
backgroundColor: "white",
shadowColor: "black",
shadowOpacity: 0.2,
shadowRadius: 3,
shadowOffset: {
height: 0,
width: 0
}
},
modalContainer: {
flex: 1,
flexDirection: "column",
justifyContent: "center",
backgroundColor: "transparent"
}
});
In the above class i have used Ex-natvigation , i have connected this class with Reducer.In Above when i am trying to update connection line by export default connect(mapStateToProps,{HitAllApis})(HomeScreen) , it shows me syntax error.
Here is what my Action class looks like :-
import * as types from '../types/ActionTypes'
import WebApi from "../component/webServiceComponent/WebServiceHandler";
function getCategorylisting() {
console.log('category');
return WebApi.GetApihit("/restapi/index/CategoryListing", null);
}
function getdashboard() {
console.log('das');
return WebApi.GetApihit("/restapi/index/getdashboard", null);
}
function getBanner() {
console.log('Banner');
return WebApi.GetApihit("/bannersliderapp/banner/banner", null);
}
function getStaticPages() {
return WebApi.GetApihit("/restapi/staticpages/getPages/", null);
}
export function HitAllApis (){
return function (dispatch) {
WebApi.fetchHeader().then(
function () {
Promise.all([
getCategorylisting(),
getdashboard(),
getBanner(),
getStaticPages()
]).then(function (response) {
dispatch({ type: types.Api_Response_case, data: response });
}, function (Error) {
dispatch({ type: types.Api_Request_case, data: response });
});
},
function (error) {
console.log(error);
}
);
}
}
I have requirement that i need to get data from multiple Api's , so i use promise in the Action class and grab data in one single response Object
My Store class :-
import {createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from '../reducers/index';
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
export default function configureStore(initialState) {
const store = createStoreWithMiddleware(rootReducer, initialState);
return store;
}
My Reducer class :-
import * as type from '../types/ActionTypes'
const initialState =({
magentoData: [],
showData: false,
isLoading: false,
popUpPageData: [],
categoryList: [],
bannerData: []
})
export default function DashBoardData(state = initialState, action = {}) {
switch (action.type) {
case type.Api_Request_case:
return state.isLoading = true;
case type.Api_Response_case:
state.isLoading = false;
state.showData=true;
state.categoryData = action.data[0];
state.magentoData = action.data[1];
state.bannerData = action.data[2];
state.popUpPageData = action.data[3];
// console.log('categoryData****', state.categoryData);
// console.log('magentoData****', state.magentoData);
// console.log('bannerData****', state.bannerData);
// console.log('popUpPageData****', state.popUpPageData);
return {...state};
default:
return state
}
}
And this is what i am getting inside my console.log("under Render ", this.props) :-
Object {
"dispatch": [Function anonymous],
"state": Object {
"DashBoardData": Object {
"bannerData": Array [],
"categoryList": Array [],
"isLoading": false,
"magentoData": Array [],
"popUpPageData": Array [],
"showData": false,
},
},
}
I might be doing wrong , please let me know is my approach is fine or i need to implement this in some other way , If i am doing anything wrong here Please let me know my mistake so that i can understand it more clearly.
Any Help would be greatly Appreciated!!!
Thanks
React re-renders when it determines that the old state is different from the new state. But you're modifying the old state and then copying it into the new state you return, so it thinks nothing has changed.
Your reducer should only read from the state object, it should not make modifications. For example:
export default function DashBoardData(state = initialState, action = {}) {
switch (action.type) {
case type.Api_Request_case:
return {
...state, // old state is NOT modified
isLoading: true // this is only set for the NEW state
};
case type.Api_Response_case:
return {
...state, // initially use what's in the OLD state,
isLoading: false, // then include the vales you are changing.
showData: true,
categoryData: action.data[0],
magentoData: action.data[0],
bannerData: action.data[0],
popUpPageData: action.data[0],
default:
// this REALLY means nothing has changed,
// React will not re-render
return state;
}
}