manually navigating Linking.Eventhandler and Linking.InitialUrl React native - android

I have an issue regarding navigating after getting the URL from either the Linking event handler or getInitialUrl.
I get the correct URL from Deep Linking, no issues there. The issue is i can't access navigate in App.js
TypeError: undefined is not an object (evaluating 'this.props.navigation.navigate')
I guess I don't have access to the navigation functions inside App.js. Any suggestion to how to solve this? I do think that I am setting the handler and retrieving it from the wrong place.
import React, { Component} from "react";
import {store} from "./store/Store";
import { Provider } from "react-redux";
import * as Font from 'expo-font';
import * as Localization from "expo-localization";
import i18n from "i18n-js";
import NavigationContainer from "./navigation/NavigationContainer";
import { SupportedLocals } from "./styles/index";
import { Linking } from "react-native";
import { refreshToken } from "./api_client/authService";
import { split } from "lodash";
i18n.locale = Localization.locale;
i18n.SupportedLocals = SupportedLocals;
i18n.fallbacks = true;
export default class App extends Component {
state = {
fontsLoaded: false,
};
constructor(props) {
super(props);
this.state = {
fontLoaded: false
};
}
async loadFonts() {
await Font.loadAsync({
RubikRegular: require("./assets/fonts/Rubik-Regular.ttf"),
RubikMedium: require("./assets/fonts/Rubik-Medium.ttf"),
RubikBold: require("./assets/fonts/Rubik-Bold.ttf"),
});
this.setState({ fontsLoaded: true });
}
componentDidMount() {
this.loadFonts();
const url = Linking.getInitialURL()
.then((url) => {
if (url) {
console.log('fired get initial url')
this.handleDeepLink({url})
}
}).catch((err) => console.error('An error occurred ', err))
Linking.addEventListener('url', this.handleDeepLink)
}
componentWillUnmount(){
Linking.removeEventListener('url', this.handleDeepLink);
}
handleDeepLink(e) {
if(e){
console.log(e)
let splitter = e.url.split("?token=")
let token = splitter[1].split("&")
let tokenSend = token[0];
console.log(tokenSend)
this.props.navigation.navigate("InvitationInfo", {token: tokenSend})
}
}
render(){
if (this.state.fontsLoaded) {
return (
<Provider store={store}>
<NavigationContainer/>
</Provider>
);
} else {
return null;
}
}
}

Related

How to remove the blank white screen in android phone in react native project?

I am new to react-native.
I am making a project in react-native with my android phone connected to my laptop at the USB port. When I run the project I see a blank screen on my phone. My phone is android version 9.
First I ran npm start and then I ran npm run android.
I ran adb -s device-name reverse tcp:8081 tcp:8081. Still the screen on my phone is blank.
This is my App.tsx file:
import "./i18n"
import React, { useState, useEffect, useRef } from "react"
import { YellowBox } from "react-native"
import { NavigationContainerRef } from "#react-navigation/native";
import { contains } from "ramda"
import { enableScreens } from "react-native-screens"
import { SafeAreaProvider, initialWindowSafeAreaInsets } from "react-native-safe-area-context";
import { RootNavigator, exitRoutes, setRootNavigation } from "./navigation"
import { useBackButtonHandler } from "./navigation/use-back-button-handler"
import { RootStore, RootStoreProvider, setupRootStore } from "./models/root-store"
import * as storage from "./utils/storage"
import getActiveRouteName from "./navigation/get-active-routename"
import { ThemeProvider } from 'react-native-elements';
import * as theme from 'theme';
enableScreens()
YellowBox.ignoreWarnings([
"componentWillMount is deprecated",
"componentWillReceiveProps is deprecated",
"Require cycle:",
])
const canExit = (routeName: string) => contains(routeName, exitRoutes)
export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE"
const App: React.FunctionComponent<{}> = () => {
const navigationRef = useRef<NavigationContainerRef>()
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined)
const [initialNavigationState, setInitialNavigationState] = useState()
const [isRestoringNavigationState, setIsRestoringNavigationState] = useState(true)
setRootNavigation(navigationRef)
useBackButtonHandler(navigationRef, canExit)
const routeNameRef = useRef()
const onNavigationStateChange = state => {
const previousRouteName = routeNameRef.current
const currentRouteName = getActiveRouteName(state)
if (previousRouteName !== currentRouteName) {
// track screens.
__DEV__ && console.tron.log(currentRouteName)
}
// Save the current route name for later comparision
routeNameRef.current = currentRouteName
// Clear the storage if we are navigating to auth stack
if ( ['register', 'login', 'forgotpassword'].includes(currentRouteName) ) {
storage.remove(NAVIGATION_PERSISTENCE_KEY);
} else {
// Persist navigation state to storage
storage.save(NAVIGATION_PERSISTENCE_KEY, state)
}
}
useEffect(() => {
(async () => {
setupRootStore().then(setRootStore)
})()
}, [])
useEffect(() => {
const restoreState = async () => {
try {
const state = await storage.load(NAVIGATION_PERSISTENCE_KEY)
if (state) {
setInitialNavigationState(state)
}
} finally {
setIsRestoringNavigationState(false)
}
}
if (isRestoringNavigationState) {
restoreState()
}
}, [isRestoringNavigationState])
if (!rootStore) {
return null
}
return (
<RootStoreProvider value={rootStore}>
<SafeAreaProvider initialSafeAreaInsets={initialWindowSafeAreaInsets}>
<ThemeProvider theme={theme}>
<RootNavigator
ref={navigationRef}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
/>
</ThemeProvider>
</SafeAreaProvider>
</RootStoreProvider>
)
}
export default App
Can anyone help?

How to handle hardware back button android in React Native?

I get a problem to handle hardware back button android in React Native, I want to back in specific page/component when I press back button in hardware/device, but I always get error 'undefined is not an object (Evaluating 'this.props.navigation').
this is my script :
import { Platform, StyleSheet, Text, View, BackHandler } from 'react-native';
import { createStackNavigator, createAppContainer, NavigationActions, createBottomTabNavigator } from 'react-navigation';
import OnBoarding from './apps/src/onBoarding/OnBoarding';
import Welcome from './apps/src/welcome/Welcome';
import Login from './apps/src/login/Login';
const MainNavigator = createStackNavigator({
OnBoarding: OnBoarding,
Welcome: Welcome,
Login: Login
},{
initialRouteName: 'OnBoarding',
headerMode: 'none',
navigationOptions: {
headerVisible: false
}
});
const Approot = createAppContainer(MainNavigator);
var screen = '';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
routeName: ''
}
}
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton() {
if(screen == 'Login') {
this.props.navigation.navigate('OnBoarding');
}else{
return false;
}
}
getActiveRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return this.getActiveRouteName(route);
}
return route.routeName;
}
render() {
return <Approot onNavigationStateChange={(prevState, currentState) => {
screen = this.getActiveRouteName(currentState)
}} />;
}
}
in this case, I have 3 component, they are OnBoarding, Welcome, Login, when postion in Login I want to back to OnBoarding when press hardware back button, please help me to solve this problem.
Thanks.
You will have to use the navigation services as you want to navigate from outside of navigation (root of the app). you can follow documentation.
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html

React Native Router flux action.key() doesn't work

I am building a project on React Native, I am new to React Native. But unfortunately the Actions.key() not working. Here is my code details
App.js
import React, { Component } from 'react';
import { AsyncStorage, ActivityIndicator } from 'react-native';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import { PersistGate } from 'redux-persist/integration/react'
import Spinner from './components/common/Spinner';
import reducers from './reducers';
import Router from './Router';
import { persistStore, persistReducer } from 'redux-persist'
class App extends Component {
render() {
renderLoading = () =>
{
<Spinner size="large" />;
}
const persistConfig = {
key: 'root2',
storage: AsyncStorage
}
const persistedReducer = persistReducer(persistConfig, reducers)
const store = createStore(persistedReducer, {}, applyMiddleware(ReduxThunk));
const persistor = persistStore(store);
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Router />
</PersistGate>
</Provider>
);
}
}
export default App;
LoginForm.js
import React, { Component } from 'react';
import { Text } from 'react-native';
import { connect } from 'react-redux';
import { emailChanged, passwordChanged, loginUser, loginUserSuccess1 } from '../actions';
import { Card, CardSection, Input, Button, Spinner } from './common';
import { Actions } from 'react-native-router-flux';
import NameEnter from './NameEnter';
import Router from '../Router';
class LoginForm extends Component {
componentDidMount()
{
console.log (this.props.userDetails);
if(this.props.userDetails != null)
{
if (this.props.userDetails.success !== false) {
console.log (this.props.userDetails.success);
this.props.loginUserSuccess1(this.props.userDetails);
}
}
//Actions.screen1();
}
onEmailChange(text) {
this.props.emailChanged(text);
}
onPasswordChange(text) {
this.props.passwordChanged(text);
}
onButtonPress() {
const { email, password } = this.props;
this.props.loginUser({ email, password });
}
renderButton() {
// if (this.props.loading) {
// return <Spinner size="large" />;
// }
return (
<Button onPress={this.onButtonPress.bind(this)}>
Login
</Button>
);
}
render() {
return (
<Card>
<CardSection>
<Input
label="Email"
placeholder="email#gmail.com"
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
/>
</CardSection>
<CardSection>
<Input
secureTextEntry
label="Password"
placeholder="password"
onChangeText={this.onPasswordChange.bind(this)}
value={this.props.password}
/>
</CardSection>
<Text style={styles.errorTextStyle}>
{this.props.error}
</Text>
<CardSection>
{this.renderButton()}
</CardSection>
</Card>
);
}
}
const styles = {
errorTextStyle: {
fontSize: 20,
alignSelf: 'center',
color: 'red'
}
};
const mapStateToProps = ({ auth }) => {
const { email, password, error, loading , userDetails } = auth;
return { email, password, error, loading , userDetails };
};
export default connect(mapStateToProps, {
emailChanged, passwordChanged, loginUser, loginUserSuccess1
})(LoginForm);
AuthActions.js
import { Actions } from 'react-native-router-flux';
import axios from 'react-native-axios';
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAIL,
LOGIN_USER,
URL
} from './types';
export const emailChanged = (text) => {
return {
type: EMAIL_CHANGED,
payload: text
};
};
export const passwordChanged = (text) => {
return {
type: PASSWORD_CHANGED,
payload: text
};
};
export const loginUser = ({ email, password }) => {
return (dispatch) => {
dispatch({ type: LOGIN_USER });
axios.post(URL + '/hostLogin', {
email: email,
password: password
})
.then((user) => {
console.log(user.data);
loginUserSuccess(dispatch, user);
})
.catch((error) => {
loginUserFail(dispatch);
});
// firebase.auth().signInWithEmailAndPassword(email, password)
// .then(user => loginUserSuccess(dispatch, user))
// .catch((error) => {
// console.log(error);
// firebase.auth().createUserWithEmailAndPassword(email, password)
// .then(user => loginUserSuccess(dispatch, user))
// .catch(() => loginUserFail(dispatch));
// });
};
};
const loginUserFail = (dispatch) => {
dispatch({ type: LOGIN_USER_FAIL });
};
const loginUserSuccess = (dispatch, user) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: user.data
});
Actions.main();
};
export const loginUserSuccess1 = (user) => {
console.log(1);
return {
type: 'Navigate',
payload: 'Success'
};
};
AuthReducer.js
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAIL,
LOGIN_USER
} from '../actions/types';
import { Actions } from 'react-native-router-flux';
const INITIAL_STATE = {
email: '',
password: '',
userDetails: null,
error: '',
loading: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case EMAIL_CHANGED:
return { ...state, email: action.payload };
case PASSWORD_CHANGED:
return { ...state, password: action.payload };
case LOGIN_USER:
return { ...state, loading: true, error: '' };
case LOGIN_USER_SUCCESS:
console.log(action.payload );
return { ...state, userDetails: action.payload };
case LOGIN_USER_FAIL:
return { ...state, error: 'Authentication Failed.', password: '', loading: false };
case 'Navigate':
console.log(2);
Actions.main();
default:
return state;
}
};
This is Router.js
import React from 'react';
import { Scene, Router, Actions } from 'react-native-router-flux';
import LoginForm from './components/LoginForm';
import NameEnter from './components/NameEnter';
const RouterComponent = () => {
return (
<Router>
<Scene key="root" hideNavBar>
<Scene key="auth" >
<Scene key="login" component={LoginForm} title="Please Login" />
</Scene>
<Scene key="main">
<Scene key="screen1" component={NameEnter} title="Please Enter Your Name " />
</Scene>
</Scene>
</Router>
);
};
export default RouterComponent;
Now in Authreducer.js i am using Actions.main() to navigate to main . But that is not working . Is something wrong with the settings of router . Please help
It is not working because you have mixed react native commands with redux. What i have noted is that redux always loads first before react native libraries etc loads so it will not work, you have to separate them to make them work.
You can separate them by returning true (also remove Actions.main() from redux file switch statements) and you will read this response from another file(this file will be react native component) where you can use Actions.main() and then it will work.
From the redux documentations:
"Given the same arguments, it [reducers] should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation."
Reducers should always stay pure. It's an antipattern to perform routing transitions inside a reducer.
Beside this: I had the same issue with react native 0.57.0 and react-native-router-flux v4, which is based on React Navigation v2.x. Every method except .key() was working, so I had to downgrade back to rnrf 4.0.0-beta.32. It doesn't seem to be a bug and is more likely caused by a dependency version mismatch (e.g. react & react-native). I will dig deeper into this issue and hopefully find a way to use the newer versions.

How to Cache API data using AsyncStorage React Native

Fairly new to React native and its concepts. I have been playing with RN for a while to create an application to fetch API data from
http://jsonplaceholder.typicode.com/photos
I have been looking into the documentation of AsyncStorage to implement how i can cache the API data so that upon terminating the application, it doesn't have to deal with fetching the data from web again and again, but wasn't successfully able to implement it.
It will be great if you can provide me help/suggestion based on it. I have included my source code for the 2 important files in my application, along with the a Test.js file with how i was trying to achieve.
import React, {Component} from 'react';
import { FlatList, View, Text, AsyncStorage, ActivityIndicator } from 'react-native';
import axios from 'axios';
import GalleryDetail from './GalleryDetail';
class GalleryList extends Component {
state = { photos: []};
componentDidMount() {
axios.get('http://jsonplaceholder.typicode.com/photos')
.then(response => this.setState({ photos: response.data }))
.catch((error)=> console.warn("fetch Error: ", error));
}
getPhotos = async()=> {
try {
photos = await AsyncStorage.getItem('GalleryPhotos');
}
catch (error) {
console.error(error);
}
}
savePhotos(){
AsyncStorage.setItem('GalleryPhotos', this.state.photos);
console.log('works !');
}
renderPhoto = ({item})=> {
return <GalleryDetail photo={item}/>
}
keyExtractor = (photo, index) => photo.id;
render () {
if(!this.state.photos){
return <ActivityIndicator/>;
}
return (
<FlatList
data = {this.state.photos}
keyExtractor={this.keyExtractor}
renderItem={this.renderPhoto}
/>
);
}
}
export default GalleryList;
and GalleryDetail linked with GalleryList-
import React, {Component} from 'react';
import { Text, View, Image } from 'react-native';
import Card from './Card';
import CardSection from './CardSection';
const GalleryDetail = (props)=> {
return (
<Card>
<CardSection style = {styles.headerContentStyle}>
<Image
style={styles.thumbnailStyle}
source = {{ uri: props.photo.thumbnailUrl}}/>
<Text style= {styles.textStyle}>{props.photo.title} </Text>
</CardSection>
</Card>
);
};
const styles = {
headerContentStyle: {
flexDirection: 'column',
justifyContent: 'space-around'
},
thumbnailStyle: {
height: 60,
width: 60
},
textStyle: {
fontSize: 12,
//textAlign: 'right',
flexDirection: 'row',
justifyContent: 'flex-end',
flex: 1,
flexWrap: 'wrap',
marginLeft: 5,
marginRight: 5,
}
}
export default GalleryDetail;
My method of trying was that-
Upon launching the application, it will first look in asyncStorage, if it finds the data- it fetches from async otherwise going to the web,fetching and storing again for later use.
I tried to implement somewhat like this in a separate file since i dint wanted to breakdown my already running app. The weird broken syntax is
State = {
photos: []
}
componentDidMount() {
// just a variable acting to fetch data from the stored keyvalue pair
check = AsyncStorage.getItem("PhotosKey").then((response) => {
this.setState({"PhotosKey": response});
}).done();
if(check) {
console.log('Data was fetched!!!!!');
check();
}
else {
console.log("Data was not fetched!");
var Data = axios.get('http://jsonplaceholder.typicode.com/photos').
then(response => this.setState({ photos: response.data })).
catch((error)=> console.warn("fetch Error: ", error));
}
}
Thanks in advance!
async componentDidMount() {
const photoStorage = await AsyncStorage.getItem('GalleryPhotos')
if(photoStorage) {
try {
const photoResp = await axios.get('http://jsonplaceholder.typicode.com/photos')
const photoData = await JSON.stringify(photoResp.data)
await AsyncStorage.setItem('GalleryPhotos', photoData);
} catch(e) {
console.warn("fetch Error: ", error)
}
.then(response => this.setState({ photos: response.data }))
}
}
later
getPhotos = async()=> {
try {
photos = JSON.parse(await AsyncStorage.getItem('GalleryPhotos'));
}
catch (error) {
console.error(error);
}
}
The approach from Subramanya is basically all you need to get started, I'm just going to introduce a state management approach with redux-persist where you can definitely appreciate when your app grows.
Redux Persist is performant, easy to implement, and easy to extend.
Let say you have your app hooked up with redux and implemented a fairly organised state tree, redux-persist stores the entire app state with AsyncStorage or any storage engine of your choice.
For instance, let's assume that your API endpoint returns a collection of photos, all you need to do is update the store, and your users can expect their data is safe and saved with redux-persist.
I have not tested all the code below
Let's define the store first,
import { AsyncStorage } from 'react-native';
import { createStore, compose, applyMiddleware, } from "redux";
import { persistStore } from "redux-persist";
import ReduxThunk from "redux-thunk";
import reducers from "../reducers"
const middleWare = [ReduxThunk]
const store = createStore(
reducers,
{},
compose(applyMiddleware(...middleWare))
)
// you can define more parameters, like blacklist or whitelist a reducer
// also, specify storage engine
persistStore(store, { storage: AsyncStorage });
export default store;
At your app's entry point,
import React, { Component } from "react";
import { Provider } from "react-redux";
import Router from "./Router";
import store from './store';
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Provider store={store}>
<Router /> // navigator
</Provider>
);
}
}
Finally, your API logic.
// action creator
export storePhoto = photos => {
return {
type: 'STORE_PHOTOS',
payload: photos
}
}
// photos reducer
import { REHYDRATE } from 'redux-persist/constants';
export default (state = {}, action) => {
switch (action.type) {
case STORE_PHOTOS:
return { ...state, photos: action.payload }
// this is where `redux-persist` handles caching
case REHYDRATE:
var incoming = action.payload;
if(incoming) return { ...state, ...incoming }
return state;
default:
return state;
}
};
To retrieve data, you will see that redux abstracts away all the excess logics and there is no more setItem, getItem because redux-persist does that automagically for your already.
import { connect } from "react-redux";
import { storePhotos } from "./actions";
class GalleryList extends Component {
async componentDidMount() {
const photos = await axios.get('http://jsonplaceholder.typicode.com/photos');
storePhoto(photos)
}
renderPhoto = ({ item }) => <GalleryDetail photo={item}/>
keyExtractor = (photo, index) => photo.id;
render () {
return (
<FlatList
data = {this.props.photos}
keyExtractor={this.keyExtractor}
renderItem={this.renderPhoto}
/>
);
}
}
// pull data from photos reducer
const mapStateToProps = ({ photos }) => {
return {
photos: photos.photos
}
}
export default connect(mapStateToProps, { storePhotos })(GalleryList);
To summarise,
Install redux-persist in you project.
Import persistStore and autoRehydrate form redux-persist.
Add autoRehydrate to your store.
Pass your store to persistStore.
Listen to the persist/REHYDRATE action on your reducer and populate state accordingly.
Hope my answer helps!
Answer
Caching the data for a specific period of time
const cacheIntervaInHours = 24
const cacheExpiryTime = new Date()
cacheExpiryTime.setHours(cacheExpiryTime.getHours() + cacheIntervalInHours)
const lastRequest = await AsyncStorage.getItem("lastRequest")
if (lastRequest == null || lastRequest > cacheExpiryTime) {
fetch(`${apiUrl}/blogPosts/recent`)
.then(async (response) => {
return await response.json()
})
.then(async (json) => {
if (!json || json.length == 0) {
throw new Error()
}
AsyncStorage.setItem("lastRequest", new Date());
return await AsyncStorage.setItem('blogPosts', JSON.stringify(json))
})
.catch(error => {
console.error(error)
})
}

React Native - Share Method Cancel Event

i am using React Native Share Method (https://facebook.github.io/react-native/docs/share.html) to share content on Android.
As per documentation, we can use it in following ways:
Share.share({
message: 'React Native | A framework for building native apps using React'
})
.then((result) => {
console.log(result.action) // returns sharedAction
})
.catch((error) => {
console.log(error)
})
So when we call Share method, we will get result in then and popup window is appeared which contains apps list with which we can share the message content.
Issue:
Popup window also contains a cancel button, so when user click on it, window get closed, but there is no method available/mentioned to capture it, in docs.
Is there any way to capture the cancel event, as i want to perform some action when user clicks on it.
Thanks in adv. :)
There is option in share dismissedAction but unfortunately this is IOS only . You can use react-native-share with prop "failOnCancel" to true and it will work on both android and ios
Sample Code
import React, { Component } from "react";
import { Button } from "react-native";
import Share from "react-native-share";
export default class ShareExample extends Component {
onShare = async () => {
const shareOptions = {
title: "Share file",
social: Share.Social.EMAIL,
failOnCancel: true
};
try {
const ShareResponse = await Share.open(shareOptions);
//setResult(JSON.stringify(ShareResponse, null, 2));
} catch (error) {
console.log("Error =>", error);
}
};
render() {
return <Button onPress={this.onShare} title="Share" />;
}
}
App Preview
This might help
import React, {Component} from 'react';
import {Share, Button} from 'react-native';
class ShareExample extends Component {
onShare = async () => {
try {
const result = await Share.share({
message:
'React Native | A framework for building native apps using React',
});
if (result.action === Share.sharedAction) {
if (result.activityType) {
// shared with activity type of result.activityType
} else {
// shared
}
} else if (result.action === Share.dismissedAction) {
// dismissed
}
} catch (error) {
alert(error.message);
}
};
render() {
return <Button onPress={this.onShare} title="Share" />;
}
}

Categories

Resources