How to add click action for push notification in React native - reactjs

I want to navigate to particular app screen after click on remote push notification.
How can i implement this with react native. I am using react-native-push-notification library

react-native-push-notification has an on notification press handler -> onNotification which is called every time the user presses or has received a notification (this is from the react-native-push-notification documentation). In an application you create a NavigationService component which uses the top level navigator reference set in your App.js file. With this navigator, in the onNotification callback you can then navigate to any screen in your application and use the notification data you have received for your purposes, which can be passed to the navigating screen through the navigator (in its state). Some psuedo code can look like this:
NotificationService.js:
import NavigationService from './NavigationService';
import PushNotification from "react-native-push-notification";
PushNotification.configure({
onNotification: function(notification) {
const { data } = notification;
NavigationService.navigate('Screen', { notificationData: data });
}
});
NavigationService.js:
import { NavigationActions } from 'react-navigation';
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
Edit: Also you have to create an AppContainer, (This is placed in your App.js)
import { createSwitchNavigator } from 'react-navigation';
import { createAppContainer } from 'react-navigation';
const AppContainer = createAppContainer(createSwitchNavigator({...Screens}));
export default class App extends React.Component {
...
render() {
return (
<View>
<AppContainer ref={ (navigatorRef) => { NavigationService.setTopLevelNavigator(navigatorRef); } } />
</View>
)
}
}

Related

In react-navigation, is there an equivalent to this.props.navigation.dispatch() without using the this.props.navigation prop?

I see that the next react-navigation version to be released will have the useNavigation() hook, but is there a way in react-navigation#4.x to effectively use this.props.navigation.dispatch() without having to use this.props?
You can use the react-navigation-hooks library with React Navigation 4.
import { useNavigation } from 'react-navigation-hooks';
function MyComponent() {
const navigation = useNavigation();
// ...
}
You can also use the withNavigation HOC from the main package: https://reactnavigation.org/docs/en/with-navigation.html
I figured out a solution myself based on modifying a recommended approach from the official react-navigation 4.x docs. The approach involves making a navigation "app container" component out of the app's stack navigator and creating a reference to that container. This reference is then used by a mediating navigation services object (here I call it NavigationService) that can be imported and used anywhere in my codebase.
// App.js
import { createStackNavigator, createAppContainer } from 'react-navigation';
import NavigationService from './NavigationService';
import HomeScreen from "./HomeScreen";
const TopLevelNavigator = createStackNavigator({
...
Home: {
screen: HomeScreen,
defaultNavigationOptions: { gesturesEnabled: isBackGestureEnabled }
}
...
});
const AppContainer = createAppContainer(TopLevelNavigator);
export default class App extends React.Component {
// ...
render() {
return (
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
);
}
}
The dispatching actions I want are defined as follows (here I have methods created for adding to my navigation stack and resetting the navigation stack). (This differs from the react-navigation docs' recommendation; I had to use _navigator.currentNavProp.dispatch() rather than _navigator.dispatch(), which didn't exist for me.)
// NavigationService.js
import { NavigationActions, StackActions } from "react-navigation";
var _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.currentNavProp.dispatch(
NavigationActions.navigate({
routeName,
params
})
);
}
function navigationReset(routeName) {
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName })]
});
_navigator.currentNavProp.dispatch(resetAction);
}
export default { navigate, navigationReset, setTopLevelNavigator };
And now I can use this in any of my other JavaScript files, whether they are React components or not.
// anyOtherFile.js
import NavigationService from './NavigationService';
...
NavigationService.navigationReset("Home");
// or
NavigationService.navigate("Home");
...

How to architect handling onSuccess of a redux dispatched request that becomes a React Navigation change of screen

I have a Registration screen.
The result of a successful registration will update the account store with the state:
{error: null, token: "acme-auth" ...}
On the Registration screen I render an error if there is one from the store.
What I want to do is navigate to the Dashboard with this.props.navigation.navigate when the store state changes.
I can do this hackily:
render() {
const {account} = this.props
const {token} = account
if (token) {
this.props.navigation.navigate('Dashboard')
}
}
I can also use callbacks:
sendRegistration = () => {
const {email, password} = this.getFormFields()
this.props.registerStart({email, password, success: this.onRegisterSuccess, failure: this.onRegisterFailure}) //using mapDispatchToProps
}
Passing the callback through the redux path seems redundant since I already have the changed state thanks to linking the account store to my Registration component props.
I am toying with the idea of a top-level renderer that detects a change in a userScreen store then swaps out the appropriate component to render.
Is there a simpler, or better way?
Yes there is a better way. If you want to navigate in an async fashion the best place to do it is directly in the thunk, sagas, etc after the async action is successful. You can do this by creating a navigation Service that uses the ref from your top level navigator to navigate.
In app.js:
import { createStackNavigator, createAppContainer } from 'react-navigation';
import NavigationService from './NavigationService';
const TopLevelNavigator = createStackNavigator({
/* ... */
});
const AppContainer = createAppContainer(TopLevelNavigator);
export default class App extends React.Component {
// ...
render() {
return (
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
);
}
}
This sets the ref of the navigator. Then in your NavigationService file:
// NavigationService.js
import { NavigationActions } from 'react-navigation';
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
// add other navigation functions that you need and export them
export default {
navigate,
setTopLevelNavigator,
};
Now you have access to the navigator and can navigate from redux directly. You can use it like this:
// any js module
import NavigationService from 'path-to-NavigationService.js';
// ...
NavigationService.navigate(''Dashboard' });
Here is the documentation explaining more:
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html

React Navigation with Auth screen in every tab

I have an app with this kind of structure
It doesn't matter if user is logged in or not, the TabBar always should be the same. In case user logged in app will open the content of the tab, otherwise Auth screen. How can I create this in react-navigation?
Sorry, slightly confused in react-navigation.
So I think I have a solution.
Create an event emitter that will emit events when the login state changes
Subscribe to the events in the App.js
Pass the updated events via screenProps to the TabNavigator.
These updated screenProps allow toggling between logged in states in each tab.
Store the logged in state in AsyncStorage so that it will be persisted between app closes. Note the AsyncStorage only allows you to store strings.
Send events from the AuthScreen when the user logs in, or from the other screens when the user logs out.
Event Emitter
To create an event emitter we need to use the events dependency. Install it with npm i events. We will create it as a singleton so that we can only have one instance of this running in our app.
import { EventEmitter } from 'events';
class LoginEventEmitter {
constructor () {
this.eventEmitter = new EventEmitter();
}
// create a function to handle the login
// pass the loggedIn value that you want to be emitted
handleLogin = (loggedIn) => {
this.eventEmitter.emit('loggedIn', { loggedIn });
}
}
const EventEmitterController = new LoginEventEmitter();
export default EventEmitterController;
App.js
Subscribe to the events and handle AsyncStorage
import React from 'react';
import { AsyncStorage } from 'react-native';
import AppContainer from './MainNavigation';
import LoginEventEmitter from './LoginEventEmitter';
export default class App extends React.Component {
constructor (props) {
super(props);
this.state = {
loggedIn: false,
loaded: false
};
// subscribe to the events and update the values in state and in AsyncStorage
LoginEventEmitter.eventEmitter.addListener('loggedIn', e => {
this.setState({ loggedIn: e.loggedIn });
let value = e.loggedIn ? 'true' : 'false';
AsyncStorage.setItem('loggedIn', value);
});
}
async componentDidMount () {
// handle the loggedIn value when the component mounts
try {
let loggedIn = await AsyncStorage.getItem('loggedIn');
if (loggedIn) {
this.setState({
loggedIn: loggedIn === 'true',
loaded: true
});
} else {
this.setState({ loaded: true });
}
} catch (error) {
console.warn(error);
}
}
render () {
// wait until asyncstorage has returned a value before showing the App.
// pass the loggedIn value via screen props so every screen in the TabNavigator gets updated with the new value
if (this.state.loaded) {
return (
<AppContainer screenProps={{ loggedIn: this.state.loggedIn }}/>
);
} else {
return null;
}
}
}
Template for Tab Screen
This is a basic template for each TabScreen. I have put a logout button on it so that the user can logout. (This is mainly for testing).
import React from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import AuthScreen from './AuthScreen';
import LoginEventEmitter from './LoginEventEmitter';
export default class Screen1 extends React.Component {
logout = () => {
LoginEventEmitter.handleLogin(false);
}
render () {
if (this.props.screenProps.loggedIn) {
return (
<View style={styles.container}>
<Text>Screen 1</Text>
<Button title={'logout'} onPress={this.logout} />
</View>
);
} else {
return <AuthScreen />;
}
}
}
Notice in the render function that we access the screenProps that are passed from the TabNavigator depending on the value that is passed depends on what is rendered.
A sample AuthScreen
Here we perform a "login" when the user taps the button. This emits an event that is caught by the listener in the App.js which in turn updates the state value in the App.js and that value is passed via screenProps to each Tab.
import React from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import LoginEventEmitter from './LoginEventEmitter';
export default class Screen1 extends React.Component {
render () {
return (
<View style={styles.container}>
<Text>AuthScreen</Text>
<Button title={'Login'} onPress={() => {
LoginEventEmitter.handleLogin(true);
}}/>
</View>
);
}
}
Sample TabNavigator
Here is a sample TabNavigator.
import Screen1 from './Screen1';
import Screen2 from './Screen2';
import { createBottomTabNavigator, createAppContainer } from 'react-navigation';
const screens = {
Screen1: {
screen: Screen1
},
Screen2: {
screen: Screen2
}
};
const config = {
headerMode: 'none',
initialRouteName: 'Screen1'
};
const MainNavigator = createBottomTabNavigator(screens, config);
export default createAppContainer(MainNavigator);
Snack
Finally here is a snack showing it all working https://snack.expo.io/#andypandy/event-emitter-to-handle-login

return from navigation in react native give me undefined in this.props.navigation.state.params.filePath

i need help :).
my project is 2 pages in react native, MainPage and SoundRecord.
my init screen is MainPage and when i press the button 'take sound'
i move to another component to record sound(i move with react native navigation).
when i come back i want to return the filePath(where it save the file..).
i want to insert it to the state.
when i do this in MainPage:
this.state{
filePath: this.props.navigation.state.params.filePath
}
it give error:
undefined is not an object(evaluating 'this.props.navigation.state.params.filePath')
and i understand this because i start my project with MainPage and i dont have the filePath from the SoundRecord page.
what can i do?
can i do check if this.props.navigation.state.params.filePath !== undefined?
how to do it? i try almost everything...
i put the relevant code:
MainPage:
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import ImagePicker from 'react-native-image-picker';
import { RNS3 } from 'react-native-aws3';
import { aws } from './keys';
import SoundRecord from './SoundRecord'
export default class MainPage extends Component {
constructor(props){
super(props);
this.state={
file:'' ,
config:'',
filePath: '',
fileName: '',
tag:''
};
}
MoveToSoundRecordPage = () => {
this.props.navigation.navigate('SoundRecord');
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.MoveToSoundRecordPage}>
<Text>take sound</Text>
</TouchableOpacity>
{/* <SoundRecord takeSound={this.takeSound}/> */}
<Text>{this.state.fileName}</Text>
<TouchableOpacity onPress={this.UploadToAWS.bind(this)}>
<Text>Upload To Aws</Text>
</TouchableOpacity>
</View>
);
}
}
SoundRecord when i finish to record i send the filePath like this:
finishRecord = (filePath) => {
this.props.navigation.navigate('MainPage',{filePath});
}
thank!
If you want to update something on the previous page then you can do it in the following way.
Create a function in the MainPage that updates the state with the new filepath.
Pass the function as as a param.
Use the passed function in SoundRecord to update the state in the MainPage
MainPage
In your MainPage add the following
sendFilepath = (filePath) => {
this.setState({filePath})
}
Update the MoveToSoundRecordPage function to take the sendFilepath as a parameter:
MoveToSoundRecordPage = () => {
this.props.navigation.navigate('SoundRecord', { sendFilepath: this.sendFilepath });
}
SoundRecord
Then in the SoundRecord page you want to update the finishRecord so that it calls the function that you have passed.
finishRecord = (filePath) => {
this.props.navigation.state.params.sendFilepath(filePath)
this.props.navigation.goBack();
}
Snack
Here is a snack https://snack.expo.io/#andypandy/navigation-passing-function that shows passing a function called sendFilepath from Screen1 to Screen2. Then in Screen2 it then calls the function that was passed and this updates the state in Screen1.
Please try this. It may help you
Add this in MainPage screen:
componentWillMount() {
const filePath = this.props.navigation.getParam('filePath', '');
this.setState({ filePath:filePath })
}

How do I conditionally handle navigation in React Redux (React Native) based on result of dispatched action?

I have the following component
import { View, Text } from 'react-native';
import React, { Component,PropTypes } from 'react';
import styles from './styles';
import { connect } from 'react-redux';
import EditProfile from '../../components/common/EditProfile'
import { saveProfile } from '../../actions/profile'
class Profile extends Component {
_saveProfile(){
//I want to to dispatch this
this.props.dispatch(saveProfile());
//and if success push this route
this.props.navigator.push({name:'Home'});
//and if not, want to show an error message
}
render() {
let {profile} = this.props;
return (
<View style={styles.container}>
<EditProfile
navigator={this.props.navigator}
profile={profile}
onSaveProfile={this._saveProfile.bind(this)}
/>
</View>
);
}
}
Profile.propTypes = {
navigator: PropTypes.object.isRequired,
};
function mapDispatchToProps(dispatch) {
return {
dispatch
};
}
function mapStateToProps(state) {
return {
profile: state.get("profile").profileState
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Profile);
I have the following action creators for this...
import facebook from '../api/facebook'
import * as types from '../constants/ActionTypes';
function saveProfileSuccess(profile){
return {
type:type.SAVE_PROFILE_SUCCESS,
profile:profile
}
}
function saveProfileError(error) {
return {
type:type.SAVE_PROFILE_ERROR,
errorMessage:error
}
}
export function saveProfile(profile){
return dispatch => {
profile.save(profile,(error,result)=> {
if(error){
dispatch(saveProfileError(error);
}else{
dispatch(saveProfileSuccess(result);
}
));
}
}
I don't have anything yet in a reducer because I am not sure how/what I need to do to conditionally handle saving a profile, handling an error message to the API, conditionally navigating to the home screen (as shown in first code snippet).
How is it that you dispatch an action from a component, and conditionally navigate away from that component on whether that action which makes an API call successfully or not. If it is not successful, I obviously want to show an error message and not navigate to another view.
Since my reputation isn't high enough, I am not eligible to comment. But I had a doubt. You aren't passing any parameter as profile in your component while calling the action method
this.props.dispatch(saveProfile()) that you are using in your action creators file
export function saveProfile(profile). Maybe I missed something.
Based on what I interpreted from your question, you want to perform navigation operations in your action methods based on conditions. If that's the case, the solution is pretty simple. Just pass the navigator reference(this.props.navigator), and the parameters required for navigating to next screen as an object, like in this case {name: 'Home'} as general method parameters to the action that you are calling to in the component.
For e.g.
In your component file, make the call as follows:
this.props.dispatch(saveProfile(profile, this.props.navigator, {name:'Home'})); // Pass the profile parameter according to your needs
And then in the action creators:
export function saveProfile(profile, appNavigator, navProps){
return dispatch => {
profile.save(profile,(error,result)=> {
if(error){
dispatch(saveProfileError(error);
}else{
dispatch(saveProfileSuccess(result);
appNavigator.push(passProps); //Will push to Home Screen
}
});
}
}

Resources