I want to pass value of a textinput from one screen to another using mapDispatchToProps. I am roughly a newbie in the redux world and I am a bit confused. kindly make corrections to my code below. I have tried using the example implemented on the documentation, however, I do not fully understand mapDispatchToProps.
PS I tried to keep the code as simple as possible for better understanding
Screen1
import React, { Component, Fragment } from 'react';
import {
View,
Text,
StyleSheet,
} from 'react-native';
import { connect } from 'react-redux';
class Screen1 extends Component {
static navigationOptions = {
header: null,
}
constructor(props) {
super(props);
this.state = {
total: 1,
};
this.onChangeText = this.onChangeText.bind(this);
}
onChangeText(number) {
const total = parseInt(number);
if (number.length === 0) {
this.setState({ total: '' });
} else {
this.setState({ total });
}
}
render() {
return (
<SafeAreaView style={styles.AndroidSafeArea}>
<View style={styles.wrapper}>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.scrollableList}
>
<InputField
children={"Receiver's phone no."}
iconType={'ios-call'}
placeholder={"number"}
keyboardType={'phone-pad'}
maxLength={11}
/>
<InputField
children={"Receiver's gifts"}
iconType={'ios-basket'}
placeholder={'Gifts'}
keyboardType={'phone-pad'}
maxLength={2}
onChangeText={this.onChangeText}
value={this.state.total.toString()}
/>
</ScrollView>
</View>
</SafeAreaView>
);
}
}
function mapDispatchToProps(dispatch) {
return {
total: () => {
dispatch(this.onChangeText());
}
}
}
export default connect(mapDispatchToProps) (Screen1);
Screen2
import React, { Component, Fragment } from 'react';
import {
View,
Text,
StyleSheet,
} from 'react-native';
import { connect } from 'react-redux';
class Screen2 extends Component {
static navigationOptions = {
header: null,
}
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<SafeAreaView style={styles.AndroidSafeArea}>
<View style={styles.wrapper}>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.scrollableList}
>
<Text>{this.props.onChangeText}</Text>
</ScrollView>
</View>
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return {
total: state.onChangeText
}
}
}
export default connect(mapStateToProps) (Screen2);
Reducer.js
import { TOTAL_GIFTS } from '../actions/types';
const initialState = {
total: ''
};
const Reducer = (state = initialState, action) => {
switch (action.type) {
case TOTAL_GIFTS:
return {
...state,
total: action.total
};
default:
return state;
}
};
export default Reducer;
Im leaving the none redux related parts of your code out.
Screen1
class Screen1 extends React.component{
handleChange(number){
this.props.announceChange(number)
}
}
//mapping dispatch to props
function mapDispatchToProps(dispatch){
announceChange(number)
}
//action creator
function announceChange(number){
return {type:'SOME_CONSTANT',payload:number}
}
export default connect(null,mapStateToProps)(Screen1)
Reducer:
export default function reducer(state={},action){
switch(action.type){
case 'SOME_CONSTANT':
return {...state,number:action.payload}
default :
return state;
}
}
screen2:
class Screen2 extends React.component{
render(){
const {number} = this.props
return(
<span>{number}</span>
)
}
}
function mapStateToProps(state){
return {
number : state.reducername
}
}
export default connect(mapStateToProps)(Screen2);
the above code is a minimal sample of the way you can use redux. if you dont have any ideas how to setup your store,reducer and other redux stuff reading this wont take more than 10 mints.
mapDispatchToProps: are functions/actions to update store(redux)
mapStateToProps : to get data from store(redux)
on first screen you will disptach action to update email using mapDispatchToProps
on second you will get email from mapStateToProps
I have created a sample code for you (CHECK IN ANDROI/IOS)
Please check https://snack.expo.io/#mehran.khan/reduxtest
APP PREVIEW
Related
I'm trying to get my head around Redux.
Here's the MainMap.js:
import React from 'react';
import {
TouchableWithoutFeedback,
StyleSheet,
Keyboard,
PermissionsAndroid,
Platform,
View,
Button,
FlatList,
Dimensions,
TouchableOpacity,
Text
} from 'react-native';
import * as placeAction from '../../store/actions/place/place';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class MainMap extends React.Component{
constructor(props){
super(props);
this.state={
//....
destinationName: [],
destinationAddress: [],
//...
};
//......
this.addPlaceHandle = this.addPlaceHandle.bind(this)
};
//....
// Dispatch a place
addPlaceHandle(name, address){
this.props.addingPlace(name, address);
}
render(){
const {container, map, listOfSearchBars, buttonView, button, next} = styles
const { destinationCoords, userLatitude, userLongitude, initial_UserLatitude, initial_UserLongitude } = this.state;
//....
return(
<TouchableWithoutFeedback onPress={this.hideKeyboard} >
<View style={container} >
<View style={buttonView}>
<TouchableOpacity style={button} onPress={() => {
this.addPlaceHandle(this.state.destinationName, this.state.destinationAddress);
}}>
<Text style={next}>Next</Text>
</TouchableOpacity>
</View>
</View>
</View>
</TouchableWithoutFeedback>
)
}
componentWillUnmount(){
Geolocation.clearWatch(this.watch_location_id);
}
}
//}
const styles = StyleSheet.create({
container:{
flex: 1
},
map:{
...StyleSheet.absoluteFillObject
},
listOfSearchBars:{
...
},
buttonView:{
...
},
button:{
...
},
next: {
...
}
});
const mapDispatchToProps = (dispatch) => ({
addingPlace: (_name, _address) => dispatch(placeAction.addPlace(_name, _address))
})
export default connect(null, mapDispatchToProps)(MainMap)
Basically, the MainMap.js is like Google Map. You search a location, choose it from the suggestions, and it'll show the direction for you.
After loads of async/await functions and this.setState(), the name and the address from the location we choose will be added to the this.state.destinationName and this.state.destinationAddress, respectively.
I've successfully done that. Now I want to dispatch that data which is the name & address to the store
place.js in actions:
export const ADD_PLACE = 'ADD_PLACE';
export const addPlace = (name, address) => {
return {
type: ADD_PLACE,
placeData: {
name: name,
address: address
}
};
};
Here's the place.js in reducers:
import { ADD_PLACE } from '../../actions/place/place';
import Place from '../../../src/models/place';
const initialState = {
places: []
};
export default (state = initialState, action) => {
switch(action.type){
case ADD_PLACE:
const newPlace = new Place(
action.placeData.name,
action.placeData.address
);
return {
places: state.places.concat(newPlace)
};
default: return state;
}
}
But I get this error when I click the button:
Any answers and recommended resources are deeply appreciated !
The problem is I did import the Place incorrectly
It has to be:
import {Place} from ''
Thanks #hussain for helping me
I'm actually working on a small react app, I actually want to connect my component to firebase, but this component contains multiple classes and multiple exports, so when i apply my method (which is based on one class component) it rendering me nothing, it supposed to returns data from firestore.
when i try to console log the state on mapStateToProps it returns undefined :
const mapStateToProps = (state) => {
console.log("state firebase",state);
return {
animationsfb: state.firestore.ordered.animations,
}
}
that's my component that contains multiple classes:
export class AnimationScreen extends Component {
render() {
return (
<View>
.........
</View>
);
}
}
const mapStateToProps = (state) => {
console.log("state firebase",state);
return {
animationsfb: state.firestore.ordered.animations,
}
}
class DetailsScreen extends React.Component {
render() {
return (
<View>
.........
</View>
);
}
}
const Navigator = FluidNavigator({
home: {screen: AnimationScreen},
homeDetails: {screen: DetailsScreen},
},
);
class HomeTransitions extends React.Component {
static router = Navigator.router;
render() {
const {navigation} = this.props;
return (
<Navigator navigation={navigation}/>
);
}
}
// it was like this before i change it: **export default HomeTransitions**
export default compose(
connect(mapStateToProps), firestoreConnect([{ collection: 'animations'}])
) (HomeTransitions);
I expect to return me data on state when i console log it, but it returns undefined.
Currently you are trying to connect everything to the store, including the navigator, which is probably not what you want to do.
If you are just using animationsfb in AnimationScreen, just connect this component to the store and use the output as a screen in your navigator:
class AnimationScreen extends Component {
render() {
return (
<View>
// [...]
</View>
);
}
}
const mapStateToProps = (state) => {
console.log("state firebase", state);
return {
animationsfb: state.firestore.ordered.animations,
}
}
const AnimationScreenConnected = connect(mapStateToProps)(AnimationScreen);
Then in your navigator:
const Navigator = FluidNavigator({
home: { screen: AnimationScreenConnected },
homeDetails: { screen: DetailsScreen },
});
I'm trying to create an product list in React where I can add and remove products.
I started to do some research on how I could do this using the redux framework/platform and react native
I already have an function productList container, product component and cartList, cartProduct component.
My problems are:
Products: I can only add products and not remove
Cart: Vice versa + the cart does not get updated on the status of the cart items.
I've added bindActionCreator but don't know how to apply it to my productList yet.
What do I expect to happen?
I'm trying to add and remove products from the react store in the same container/component.
How can I do this? Is my approach correct or am I completely wrong ?
A thank you in advance.
ProductActionCreators
export const ADD_TO_CART = 'ADD_TO_CART'
export const REMOVE_FROM_CART = 'REMOVE_FROM_CART'
export function addItemToCart(row) {
return {
type:'ADD_TO_CART',
payload: row, qty
}
}
export function removeTodo(row) {
return {
type:'REMOVE_FROM_CART' ,
payload: row, qty
}
}
ProductList(simplified)
import React from 'react';
import { Component } from 'react';
import {
View,
StyleSheet,
Text
} from 'react-native';
import Products from '../components/Products';
import { bindActionCreators} from 'redux';
import { connect } from 'react-redux';
import * as ProductActionCreators from '../actions/ProductActionCreators'
export class ProductList extends React.Component {
static navigationOptions = {
header: null,
};
constructor(props) {
super(props);
const { rows } = this.props.navigation.state.params;
const arrays = Object.values( {rows});
this.state = {
arrays,
filteredProducts: arrays,
};
const { dispatch } = props
this.boundActionCreators = bindActionCreators(ProductActionCreators, dispatch)
console.log(this.boundActionCreators)
}
render() {
return (
<View style={styles.container} >
<Text style={styles.title} >
{this.state.arrays[0].name}
</Text>
<Products products={this.state.arrays[0].data} onPress=
//Trying to change this to multiple actions
{this.props.addItemToCart}/>
</View>
)
}
}
const qty = 0;
const mapDispatchToProps = (dispatch) =>{
//need to add BindActionCreator
return{
addItemToCart:(row) => dispatch({
type:'ADD_TO_CART', payload: row, qty
}),
removeItem:(product) => dispatch ({
type:'REMOVE_FROM_CART' , payload: product, qty
})
}
}
export default connect(null, mapDispatchToProps) (ProductList);
Product(simplified)
import React, { Component } from "react";
import {
View,
Text,
TouchableOpacity,
TextInput,
FlatList,
} from "react-native";
import Icon from "react-native-vector-icons/Ionicons";
class Products extends Component {
constructor(props) {
super(props);
const { products } = this.props;
this.state = {
products,
filteredProducts: products,
};
}
renderProducts = (products) => {
return (
<View key={products.index}>
<View>
<Icon name={products.item.icon} color="#DD016B" size={25} />
</View>
<View>
<Text style={styles.name}>
{products.item.name}
</Text>
<Text>
€ {products.item.price}
</Text>
</View>
<View style={styles.buttonContainer}>
<TouchableOpacity onPress={() => this.props.onPress(products.item)} >
<Icon name="ios-add" color="white" size={25} />
</TouchableOpacity>
<TouchableOpacity onPress={() => this.props.onPress(products.item)} >
<Icon name="ios-remove" color="white" size={25} />
</TouchableOpacity>
</View>
</View>
)
}
render() {
return (
<View>
<FlatList
style={styles.listContainer}
data={this.state.filteredProducts}
renderItem={this.renderProducts}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
}
export default Products;
reducers/cartItems
const cartItems = (state = [], action) => {
switch (action.type)
{
case 'ADD_TO_CART':
if (state.some(cartItem => cartItem.id === action.payload.id)) {
// increase qty if item already exists in cart
return state.map(cartItem => (
cartItem.id === action.payload.id ? { ...cartItem, qty: cartItem.qty + 1 } : cartItem
));
}
return [...state, { ...action.payload, qty: 1 }];
// else add the new item to cart
case 'REMOVE_FROM_CART':
return state
.map(cartItem => (cartItem.id === action.payload.id ? { ...cartItem, qty: cartItem.qty - 1 } : cartItem))
.filter(cartItem => cartItem.qty > 0);
}
return state
}
export default cartItems
store/Index
import {createStore} from 'redux';
import cartItems from '../reducers/carItems';
export default store = createStore(cartItems)
App structure (simplified)
Main folder
↳
Containers(folder)
↳
ProductsList.js
CartList.js
Components(folder)
↳
Product.js
cartProduct.js
Reducers(folder)
↳
carItems.js
Actions(folder)
↳
ProductActionCreators.js
Navigation(folder)
↳
AppNavigator,js
MainTabNavigator.js
Assets(folder for images etc.)
Store(folder)
↳
index.js
App.JS
Data.JS (using static JSON file for this development phase)
You have two different actions, addItemToCart, removeItem which you define in mapDispatchToProps. Now that you specify a mapDispatchToProps argument to connect, the dispatch method is not available as a prop to the connected component, instead the method returns by mapDispatchToProps are only available
Second, you don't need to use bindActionCreators and definitely not in the component. MapDispatchToProps can simply be an object and connect will use dispatch internally.
Third, you need to pass the add and remove actions both to the child component.
Fourth you can pass multiple actions on to Product component simply as prop
Your code would look like
ProductActionCreators.js
export const ADD_TO_CART = 'ADD_TO_CART'
export const REMOVE_FROM_CART = 'REMOVE_FROM_CART'
export function addItemToCart(row) {
return {
type:'ADD_TO_CART',
payload: row
}
}
export function removeItem(item) {
return {
type:'REMOVE_FROM_CART' ,
payload: item
}
}
ProductList
import React from 'react';
import { Component } from 'react';
import {
View,
StyleSheet,
Text
} from 'react-native';
import Products from '../components/Products';
import { connect } from 'react-redux';
import { addItemToCart, removeItem } from '../actions/ProductActionCreators';
export class ProductList extends React.Component {
static navigationOptions = {
header: null,
};
constructor(props) {
super(props);
const { rows } = this.props.navigation.state.params;
const arrays = Object.values( {rows});
this.state = {
arrays,
filteredProducts: arrays,
};
}
render() {
return (
<View style={styles.container} >
<Text style={styles.title} >
{this.state.arrays[0].name}
</Text>
<Products products={this.state.arrays[0].data} addItemToCart={this.props.addItemToCart} removeItem={this.props.removeItem}/>
</View>
)
}
}
const mapDispatchToProps = {
addItemToCart,
removeItem
}
export default connect(null, mapDispatchToProps) (ProductList);
Product
import React, { Component } from "react";
import {
View,
Text,
TouchableOpacity,
TextInput,
FlatList,
} from "react-native";
import Icon from "react-native-vector-icons/Ionicons";
class Products extends Component {
constructor(props) {
super(props);
const { products } = this.props;
this.state = {
products,
filteredProducts: products,
};
}
renderProducts = (products) => {
return (
<View key={products.index}>
<View>
<Icon name={products.item.icon} color="#DD016B" size={25} />
</View>
<View>
<Text style={styles.name}>
{products.item.name}
</Text>
<Text>
€ {products.item.price}
</Text>
</View>
<View style={styles.buttonContainer}>
<TouchableOpacity onPress={() => this.props.addItemToCart(products.item)} >
<Icon name="ios-add" color="white" size={25} />
</TouchableOpacity>
<TouchableOpacity onPress={() => this.props.removeItem(products.item)} >
<Icon name="ios-remove" color="white" size={25} />
</TouchableOpacity>
</View>
</View>
)
}
render() {
return (
<View>
<FlatList
style={styles.listContainer}
data={this.state.filteredProducts}
renderItem={this.renderProducts}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
}
export default Products;
Code looks alright for most part.
The react-redux connect part in ProductList looks off. qty is always 0. It should be 1.
Also, mapStateToProps should be present to get the products from the cart.
In your ProductList, I'd approach the action binding like so:
const mapDispatchToProps = (dispatch) =>{
return bindActionCreators({
addItemToCart: (row, qty) => dispatch({
type:'ADD_TO_CART', payload: {row, qty}
}),
removeItem: (product, qty) => dispatch({
type:'REMOVE_FROM_CART' , payload: {product, qty}
})
})
}
export default connect(null, mapDispatchToProps)(ProductList);
Remove the action binding from your component's constructor as that's unnecessary.
You may want to split the code out into a Container/Component/ HOC approach, as I find it makes code far easier to read. As you've defined your actions in separate file, I'd also import these rather than redeclaring them.
If you follow this advice, you'd end up with the following:
container.js
import { bindActionCreators } from 'redux';
import ProductList from './product-list';
// Actions
import { addItemToCart, removeItem } from './actions';
function mapStateToProps(state) {
return {}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
addItemToCart,
removeItem,
})
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductList);
product-list.js
import React from 'react';
import { View, Text } from 'react-native';
import Products from '../components/Products';
export class ProductList extends React.Component {
static navigationOptions = {
header: null,
};
constructor(props) {
super(props);
const { rows } = this.props.navigation.state.params;
const arrays = Object.values( {rows});
this.state = {
arrays,
filteredProducts: arrays,
};
this.handleProductPress = this.handleProductPress.bind(this);
}
handleProductPress(e) {
e.preventDefault();
// This is pseudo code...
this.props.addItemToCart(e.target.value, 1);
return;
}
render() {
return (
<View style={styles.container} >
<Text style={styles.title} >
{this.state.arrays[0].name}
</Text>
<Products products={this.state.arrays[0].data} onPress={this.handleProductPress} />
</View>
)
}
}
export default ProductList;
Have a play with that and see how you get on.
Hey guys im just start learning about React native context api I want to know that how can I implement this as globally like global state
and its also not working after navigate to another screen and why do we include class name in provider <ProfileScreen screen= {this.state.contextData}/> can we do it globally..
here's my code
global.cart=1
const Context = React.createContext(global.cart)
class HomeScreen extends Component<Props> {
constructor(props){
super(props);
this.state={
contextData:5
}
}
Incrementhome=()=>{
this.setState({contextData:global.cart})
global.cart++
}
Decrementhome=()=>{
this.setState({contextData:global.cart})
global.cart--
}
render() {
return (
<View>
<Context.Provider value={this.state.contextData}>
<Button title="Incrementhome"
onPress={this.Incrementhome}/>
<Button title="decrementhome"
onPress={this.Decrementhome}/>
<ProfileScreen screen= {this.state.contextData}/>
</Context.Provider>
<Button title='sd' onPress={()=>{this.props.navigation.navigate('Profile')}}/>
</View>
)
}
}
class profile screen which can show my data
class ProfileScreen extends Component<Props> {
render() {
return (
<View style={{}}>
<Context.Consumer>
{data=> <Text style={{fontSize:50}}>{data}</Text>}
</Context.Consumer>
</View>
);
}
}
class profile screens that is also a provider
class ProfileScreens extends Component<Props> {
static navigationOptions =
{
title: 'MainActivity', header: <Button title='sd' onPress={()=>{this.props.navigation.navigate('ProfileScreen')}}/>
};
constructor(props){
super(props);
this.state={contextData:0
}
}
render() {
return (
<View >
<Context.Provider value={this.state.contextData}>
<Button title="decrement" onPress={()=>{ this.props.changeHomeScreen() }}/>
<Button title='sd' onPress={()=>{this.props.navigation.navigate(Profile)}}/>
</Context.Provider>
</View>
);
}
}
my navigator
export default HomeScreen = createStackNavigator({
HomeScreen:{
screen:HomeScreen
},
Profile:{
screen:ProfileScreen
},
ProfileScreens:{
screen:ProfileScreens
},
})
Sorry but you didn’t implement well the React Context API. Read this https://medium.com/#mcssym/react-context-api-why-you-dont-surely-need-redux-co-e6d96ca8abca?source=linkShare-1d75ea07b723-1539164899
The way you pass contextData via screen prop is useless if you use Context.Consumer.
The navigation.navigate take a string not a React Component as parameter.
I Don't really know how to explain you easily so i'll rewrite your code with how you must do that job.
YOUR NAVIGATOR (somewhere/navigation.js)
export default Home = createStackNavigator({
HomeScreen:{
screen: HomeScreen
},
Profile:{
screen: ProfileScreen
},
ProfileScreens:{
screen: ProfileScreens // Don't need to be a Provider
},
})
Your ProfileScreens Don't need to be a Provider because you don't use it as a wrapper. But can be a Consumer because you use the contextData. I guess it's the same as in Your HomeScreen and the one you want to make global.
//IMPORTANT
import { withHomeContext } from './somewhere/contexts/home';
class ProfileScreens extends Component<Props> {
static navigationOptions = {
title: 'MainActivity',
header: <Button title='sd' onPress={()=> this.props.navigation.navigate('ProfileScreen')}/>
};
constructor(props){
super(props);
this.state = {
contextData: props.homeProvider.contextData // Get from global context home provider
};
}
decrementHome = () => {
// Calling decrement from homeProvider
if(this.props.homeProvider) this.props.homeProvider.decrement();
}
render() {
return (
<View >
{/*You must call the decrementHome from your provider*/}
<Button title="decrement" onPress={this.decrementHome}/>
<Button title='sd' onPress={()=> this.props.navigation.navigate('ProfileScreen') }/>
</View>
);
}
}
export default withHomeContext(ProfileScreens);
YOUR ProfileScreen.You must change the way you create it as a Consumer. A better to use a function withHomeContext created in your HomeContext class.
//IMPORTANT
import { withHomeContext } from './somewhere/contexts/home';
class ProfileScreen extends Component<Props> {
render() {
return (
<View style={{}}>
<Text style={{fontSize:50}}>{this.props.homeProvider.contextData}</Text>
</View>
);
}
}
export default withHomeContext(ProfileScreen);
And finally your HomeContext with your Provider and Consumer could be:
// In Your context/home.js
const HomeContext = React.createContext();
export class HomeProvider extends React.Component {
state = {
contextData: 5 //Default Value
};
decrementHome = () => {
this.setState(prevState => {
contextData: prevState.contextData - 1;
});
}
incrementHome = () => {
this.setState(prevState => {
contextData: prevState.contextData + 1;
});
}
getValues = () => {
return {
contextData: this.state.contextData,
decrement: this.decrementHome, // Call via homeProvider prop
increment: this.incrementHome // Call via homeProvider prop
}
}
render() {
return (
<HomeContext.Provider value={this.getValues()}>
{this.props.children}
</HomeContext.Provider>
);
}
}
export function withHomeContext(Component) {
class ComponentWithContext extends React.Component {
render {
return (
<HomeContext.Consumer>
{(value) => <Component {...this.props} homeProvider={value} />
</HomeContext.Consumer>
);
};
}
return ComponentWithContext;
}
In Your root App now
import { HomeProvider } from './somwhere/context/home';
import Home from './somwhere/navigation';
export default class App extends React.Component {
render() {
return (
<HomeProvider>
<Home />
</HomeProvider>
);
}
}
I have 3 components:
ComponentA
ComponentB
BackPressHandlingComponent
BackPressHandlingComponent deals with back press.
When back pressed from ComponentA; I must exit the app.
When back pressed from ComponentB; I must go to ComponentA.
Here is my BackPressHandlingComponent code -
import { BackHandler } from 'react-native';
export class BackPressHandlingComponent extends Component {
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
}
}
My question is -
How do I tell BackPressHandlingComponent from Component A that I must exit app and from Component B that I need to go back to Component A
As per your use case, I would have addedBackpress event listeners on ComponentA and ComponentB, such that when you are on ComponentA when the callback is called you can exit the app and when in ComponentB its callback is called you can navigate to ComponentA.
Simple demo for above solution:
App.js
/**
*
* #format
* #flow
*/
import React, { Component } from 'react';
import { Text, View, TouchableOpacity, StyleSheet } from 'react-native';
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
import BackHandlerHOC from './BackHandlerHOC'
type Props = {};
export default class App extends Component<Props> {
state = {
render: 'A'
}
toggleComponent = () => {
let component = 'A'
if (this.state.render === 'A') {
component = 'B'
}
this.setState({ render: component })
}
render() {
const { render } = this.state
const wrappercomponent = render === 'A' ? (
<BackHandlerHOC
name="ComponentA"
Component={ComponentA}
/>
) : (
<BackHandlerHOC
name="ComponentB"
Component={ComponentB}
/>
)
return (
<View style={styles.container}>
<TouchableOpacity
onPress={() => this.toggleComponent()}
>
<Text> Change </Text>
</TouchableOpacity>
{wrappercomponent}
</View>
)
}
}
const styles = StyleSheet.create({
container: {
padding: 20
}
})
ComponentA
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class ComponentA extends Component {
render() {
return (
<View>
<Text>A</Text>
</View>
);
}
}
export default ComponentA;
ComponentB
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class ComponentB extends Component {
render() {
return (
<View>
<Text>B</Text>
</View>
);
}
}
export default ComponentB;
BackHandlerHOC
import React, { Component } from 'react';
import { BackHandler, ToastAndroid, View, Text } from 'react-native';
class BackHandlerHOC extends Component {
componentDidMount = () => {
BackHandler.addEventListener('hardwareBackPress', this.backPressHandler);
};
componentWillUnmount = () => {
BackHandler.removeEventListener('hardwareBackPress', this.backPressHandler);
};
backPressHandler = () => {
const { name } = this.props;
if (name === 'ComponentA') {
BackHandler.exitApp()
} else {
// this.props.navigator.resetTo({
// screen: 'ComponentA'
// })
ToastAndroid.show('will go back to A', 0);
}
return true;
};
render() {
const { Component } = this.props;
return (
<View>
<Text>Hello from backpress</Text>
<Component />
</View>
);
}
}
export default BackHandlerHOC;
You can also find the working example on expo here
Hope this helps
Just to add another approach,
I made use of the react-navigation lifecycle events,and the hardwareBackPress event, mind you the version of react-navigation here is 3.x.x.
The lifecycle event onWillFocus is called when the screen comes in view and the life-cycle event onWillBlur is called when the user is moving on to another screen, here somehow the React lifecycle events are in the hands of react-navigation, hence cannot use them here see https://reactnavigation.org/docs/3.x/navigation-lifecycle.
Following is the code:
import { BackHandler,Alert } from "react-native";
import { NavigationEvents } from 'react-navigation';
class SomeComponent {
//...my componentDidMount etc and other methods.....
backButtonAction(){
Alert.alert(
"Confirm Exit",
"Do you want to exit the app?",
[
{
text: "No",
onPress: () => {},
style: "cancel"
},
{ text: "Yes", onPress: () => BackHandler.exitApp() }
],
{ cancelable: false }
);
return true; // coz the event handler needs to return boolean.
};
setBackButtonAction(){
BackHandler.addEventListener(
"hardwareBackPress",
this.backButtonAction
);
}
removeBackButtonAction(){
BackHandler.removeEventListener(
"hardwareBackPress",
this.backButtonAction
);
}
render() {
return (
<Container>
<NavigationEvents
onWillFocus={payload => this.setBackButtonAction()}
onWillBlur={payload => this.removeBackButtonAction()}
/> //..... my view code
</Container>)
}
}