Not getting values in props when state get changed - react-native

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;
}
}

Related

React Native Modal not displaying using context and redux

I created a custom ModalContext.js but my test modal does not seem to be showing when I trigger a button's onPress
// ModalContext.js
import createDataContext from './createDataContext';
const modalReducer = (state, action) => {
switch (action.type) {
case 'openModal':
return { ...state, component: action.payload };
case 'hideModal':
return { ...state, component: null };
default:
return state;
}
};
const openModal = (dispatch) => (component) => {
console.log('hey there');
dispatch({ type: 'openModal', payload: component });
};
const hideModal = (dispatch) => () => {
dispatch({ type: 'hideModal' });
};
export const { Provider, Context } = createDataContext(
modalReducer,
{
openModal,
hideModal,
},
{ component: null }
);
// createDataContext.js
import React, { useReducer } from 'react';
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
// App.js
const App = createAppContainer(navigator);
export default () => {
return (
<ModalProvider>
<AuthProvider>
<App
ref={(navigator) => {
setNavigator(navigator);
}}
/>
</AuthProvider>
</ModalProvider>
);
};
I have a button on a test screen to check if it works or not.
// WebViewScreen.js
import React, { useState, useContext } from 'react';
import { StyleSheet, Modal, View, Text, Button } from 'react-native';
import { Context as ModalContext } from '../context/ModalContext';
const WebViewScreen = ({ navigation }) => {
const { state, openModal } = useContext(ModalContext);
const errorModal = (
<View>
<Modal animationType='slide' visible={true}>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text>Hello</Text>
</View>
</View>
</Modal>
</View>
);
return (
<>
<Button
onPress={() => {
openModal(errorModal);
}}
title='button'
/>
</>
);
};
WebViewScreen.navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('title'),
};
};
const styles = StyleSheet.create({
view: {
backgroundColor: '#f1f3f4',
},
centeredView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 22,
},
modalView: {
margin: 20,
backgroundColor: 'white',
borderRadius: 20,
padding: 35,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
},
});
export default WebViewScreen;
It seems to actually call the function as I can see "hey there" on the console but no modals appear.
I am not sure this is what you are looking for or not but I manage my details like following.
class Abc {
var1;
var2;
var3 = new B();
var4 = new C();
var5 = {}
var6 = [];
success = a => {
//your logic
}
}
class B {
varA;
varB;
varC = false;
}
I hope this is helpful.

React Native, how to execute a component's method from App.js when using stack navigation

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

How to make a QR code scanner in React native using expo?

When I run https://snack.expo.io/#sushil62/qr-code-scanner in the expo which works fine, but when copy the same code given in App.js file, after running the application the camera opens but it shows no result while scanning, and
in expo also when changing the snack version 33 or higher it does not work there too.
import React, { Component } from 'react';
import { Alert, Linking, Dimensions, LayoutAnimation, Text, View, StatusBar, StyleSheet, TouchableOpacity } from 'react-native';
import { BarCodeScanner, Permissions } from 'expo';
export default class App extends Component {
state = {
hasCameraPermission: null,
lastScannedUrl: null,
};
componentDidMount() {
this._requestCameraPermission();
}
_requestCameraPermission = async () => {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({
hasCameraPermission: status === 'granted',
});
};
_handleBarCodeRead = result => {
if (result.data !== this.state.lastScannedUrl) {
LayoutAnimation.spring();
this.setState({ lastScannedUrl: result.data });
}
};
render() {
return (
<View style={styles.container}>
{this.state.hasCameraPermission === null
? <Text>Requesting for camera permission</Text>
: this.state.hasCameraPermission === false
? <Text style={{ color: '#fff' }}>
Camera permission is not granted
</Text>
: <BarCodeScanner
onBarCodeRead={this._handleBarCodeRead}
style={{
height: Dimensions.get('window').height,
width: Dimensions.get('window').width,
}}
/>}
{this._maybeRenderUrl()}
<StatusBar hidden />
</View>
);
}
_handlePressUrl = () => {
Alert.alert(
'Open this URL?',
this.state.lastScannedUrl,
[
{
text: 'Yes',
onPress: () => Linking.openURL(this.state.lastScannedUrl),
},
{ text: 'No', onPress: () => {} },
],
{ cancellable: false }
);
};
_handlePressCancel = () => {
this.setState({ lastScannedUrl: null });
};
_maybeRenderUrl = () => {
if (!this.state.lastScannedUrl) {
return;
}
return (
<View style={styles.bottomBar}>
<TouchableOpacity style={styles.url} onPress={this._handlePressUrl}>
<Text numberOfLines={1} style={styles.urlText}>
{this.state.lastScannedUrl}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.cancelButton}
onPress={this._handlePressCancel}>
<Text style={styles.cancelButtonText}>
Cancel
</Text>
</TouchableOpacity>
</View>
);
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#000',
},
bottomBar: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
padding: 15,
flexDirection: 'row',
},
url: {
flex: 1,
},
urlText: {
color: '#fff',
fontSize: 20,
},
cancelButton: {
marginLeft: 10,
alignItems: 'center',
justifyContent: 'center',
},
cancelButtonText: {
color: 'rgba(255,255,255,0.8)',
fontSize: 18,
},
});
It would be very nice if someone suggests me to solve this or give me an example(such as downgrading the expo version) so that I can implement this.
You can use
expo-barcode-scanner
Run expo install expo-barcode-scanner
Usage
You must request permission to access the user's camera before attempting to get it. To do this, you will want to use the Permissions API. You can see this in practice in the following example.
import * as React from 'react';
import {
Text,
View,
StyleSheet,
Button
} from 'react-native';
import Constants from 'expo-constants';
import * as Permissions from 'expo-permissions';
import {
BarCodeScanner
} from 'expo-barcode-scanner';
export default class BarcodeScannerExample extends React.Component {
state = {
hasCameraPermission: null,
scanned: false,
};
async componentDidMount() {
this.getPermissionsAsync();
}
getPermissionsAsync = async() => {
const {
status
} = await Permissions.askAsync(Permissions.CAMERA);
this.setState({
hasCameraPermission: status === 'granted'
});
};
render() {
const {
hasCameraPermission,
scanned
} = this.state;
if (hasCameraPermission === null) {
return <Text > Requesting
for camera permission < /Text>;
}
if (hasCameraPermission === false) {
return <Text > No access to camera < /Text>;
}
return ( <
View style = {
{
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-end',
}
} >
<
BarCodeScanner onBarCodeScanned = {
scanned ? undefined : this.handleBarCodeScanned
}
style = {
StyleSheet.absoluteFillObject
}
/>
{
scanned && ( <
Button title = {
'Tap to Scan Again'
}
onPress = {
() => this.setState({
scanned: false
})
}
/>
)
} <
/View>
);
}
handleBarCodeScanned = ({
type,
data
}) => {
this.setState({
scanned: true
});
alert(`Bar code with type ${type} and data ${data} has been scanned!`);
};
}
Note: Passing undefined to the onBarCodeScanned prop will result in no scanning. This can be used to effectively "pause" the scanner so that it doesn't continually scan even after data has been retrieved.
Allow all the permisions which gets popped.
You're good to go!!
Hope this helps.

Could not navigate to another screen when using token in React Native

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.

Slow state update in redux

So I have this problem where a variable update is slower than a route change.
When I have an error on for example my registration view, the error shows up instantly. When press back to come back to the login view, the error is being reset (action "clearErrors" is being fired on componentWillUnmount) to an empty string via an action. The problem is that I can se the error message on the login for a brief moment before it receives the new empty error state.
ErrorReducer.js
import {
ERROR,
CLR_ERROR
} from '../actions/types';
const INIT_STATE = {
error: ''
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case ERROR:
return { ...state, error: action.payload };
case CLR_ERROR:
return { ...state, error: '' };
default:
return state;
}
};
error.js (actions)
import { CLR_ERROR } from './types';
export const clearErrors = () => {
return (dispatch) => {
dispatch({ type: CLR_ERROR });
};
};
LoginForm.js
import React, { Component } from 'react';
import { View } from 'react-native';
import { Actions } from 'react-native-router-flux';
import { connect } from 'react-redux';
import { emailChanged, passwordChanged, loginUser, resetRoute, autoLogin } from '../actions';
import { Button, Input, Message } from './common';
class LoginForm extends Component {
componentWillUnmount() {
this.props.resetRoute();
}
onEmailChange(text) {
this.props.emailChanged(text);
}
onPasswordChange(text) {
this.props.passwordChanged(text);
}
onButtonPress() {
this.props.loading = true;
const { email, password } = this.props;
this.props.loginUser({ email, password });
}
render() {
return (
<View
style={{
flex: 1,
marginLeft: 10,
marginRight: 10,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}}
keyboardShouldPersistTaps="always"
keyboardDismissMode="on-drag"
>
<Message
type="danger"
message={this.props.error}
/>
<Input
placeholder="din#email.se"
keyboardType="email-address"
returnKeyType="next"
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
icon="ios-mail"
/>
<Input
secureTextEntry
placeholder="ditt lösenord"
onChangeText={this.onPasswordChange.bind(this)}
value={this.props.password}
icon="ios-key"
iconSize={22}
/>
<Button
loading={this.props.loading}
uppercase
color="primary"
label="Logga in"
onPress={this.onButtonPress.bind(this)}
/>
<Button
fontColor="primary"
label="Registrera"
onPress={() => Actions.register()}
/>
<Button
fontColor="primary"
label="Glömt lösenord"
onPress={() => Actions.resetpw()}
/>
</View>
);
}
}
const mapStateToProps = ({ auth, errors }) => {
const { email, password, loading, token } = auth;
const { error } = errors;
return { email, password, error, loading, token };
};
export default connect(mapStateToProps, {
emailChanged, passwordChanged, loginUser, resetRoute, autoLogin
})(LoginForm);
Message.js (component to show error)
import React from 'react';
import { View, Text } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import { colors } from '../style';
export const Message = ({ type, message }) => {
const style = {
view: {
alignSelf: 'stretch',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
padding: 20,
margin: 15,
backgroundColor: colors[type],
borderRadius: 3,
elevation: 5,
shadowRadius: 5,
shadowColor: colors.smoothBlack,
shadowOffset: { width: 2.5, height: 2.5 },
shadowOpacity: 0.5
},
text: {
color: colors.alternative,
fontSize: 12,
alignSelf: 'center',
flex: 1
},
icon: {
marginRight: 20,
marginLeft: 0,
marginTop: 2,
alignSelf: 'center'
}
};
const getIcon = (iconType) => {
switch (iconType) {
case 'info':
return 'ios-information-circle';
case 'success':
return 'ios-checkmark-circle';
case 'danger':
return 'ios-alert';
case 'warning':
return 'ios-warning';
default:
return;
}
};
if (message.length > 0) {
return (
<View style={style.view}>
{(type) ? <Icon name={getIcon(type)} size={20} style={style.icon} /> : null}
<Text style={style.text}>{message}</Text>
</View>
);
}
return <View />;
};
I am running on device OnePlus3 with all console.logs removed, production build.
From what I have read, this should be fast. Am I doing something wrong here?
It's impossible to say without looking at your rendering code, but it's likely that the slowness is not caused by the time it takes for redux to update the state, but for React to re-render the UI after the dispatch has completed - possibly because it's busy re-rendering other things while your navigator is transitioning.
To guarantee the ordering of actions with redux-thunk, you can return a Promise from your thunk action creator and wait to navigate back until the action has been dispatched:
export const clearErrors = () => {
return (dispatch) => {
return new Promise(dispatch({ type: CLR_ERROR }));
};
};
In your view then, you can do the back navigation action once the error has been cleared:
// assuming the action creator has been passed
// to the component as props
this.props.clearErrors().then(() => navigator.back());