React Native Redux - Not showing updated state via different route - react-native

I'm writing a react-native app via expo and trying to implement redux. I'm not sure if I'm going about this completely the wrong way. I have a home page that has two sections, a search text box and an area with links to content pages. The content pages also contain the same search box component. I want to be able to pass the contents of the search box input to the content page so that the user doesn't need to enter this again (and will probably require access to this content further in the user journey)
My app.js looks like the below:
import React from 'react';
import 'react-native-gesture-handler';
import { createStackNavigator } from 'react-navigation-stack';
import { createAppContainer } from 'react-navigation';
import ContentPage from './pages/ContentPage.js';
import LogoTitle from './components/LogoTitle';
import EventsListPage from './pages/EventsListPage.js';
import EventPage from './pages/EventPage';
import VenuePage from './pages/VenuePage';
import HomeScreen from './pages/HomePage';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
const initialState ={
postcode:"abcefg"
}
const reducer = (state = initialState, action) => {
switch(action.type)
{
case 'SET_POSTCODE':
return {
postcode: action.text
}
default:
console.log("returning default state")
return state
}
}
const store = createStore(reducer);
const RootStack = createStackNavigator(
{
Home: HomeScreen,
ContentPage: ContentPage,
EventsListPage: EventsListPage,
EventPage: EventPage,
VenuePage: VenuePage
},
{
initialRouteName: 'Home',
defaultNavigationOptions: {
headerTitle: () => <LogoTitle />,
headerLeft: () => null,
headerStyle: {
backgroundColor: '#ADD8E6'
}
},
}
);
const AppContainer = createAppContainer(RootStack);
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppContainer />
</Provider>);
}
}
Homepage.js:
import React from 'react';
import { StyleSheet, View } from 'react-native';
import SearchBox from '../components/SearchBox'
import TypeDrillDownArea from '../components/TypeDrillDownArea'
import 'react-native-gesture-handler';
class HomeScreen extends React.Component {
constructor(props) {
super(props);
state = {
};
}
render() {
return (
<View style={styles.container}>
<SearchBox navigation={this.props.navigation} eventTypeId=''/>
<TypeDrillDownArea navigation={this.props.navigation} />
</View>
);
}
}
export default HomeScreen
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'stretch'
},
});
Relevant searchbox.js:
render() {
return (
<ImageBackground source={topperBackground} style={{width: '100%'}}>
<View>
<View style={styles.row}>
<View>
<TextInput
value={this.props.postcode}
autoCapitalize="characters"
style={styles.inputBox}
placeholder="Enter Postcode"
onChangeText={(e) => this.props.setPostcode(e)}
/>
</View>
<View>
<TouchableOpacity
disabled={this.state.locationDisabled}
onPress={() => {
this.props.navigation.navigate('EventsListPage', {
navigation: this.props.navigation,
eventTypeId: this.state.eventTypeId,
locLongitude: this.state.location.coords.longitude,
locLatitude: this.state.location.coords.latitude,
});
}}>
<Image
style={styles.locationPin}
source={locationPin}
/>
</TouchableOpacity>
</View>
</View>
<View style={styles.searchButtonArea}>
<TouchableOpacity
onPress={() => {
console.log("postcode is: " + this.state.postcode)
this.props.navigation.navigate('EventsListPage', {
eventTypeId: this.state.eventTypeId,
postcode: this.props.postcode,
});
}}>
<Text style={styles.searchButton}>SEARCH</Text>
</TouchableOpacity>
</View>
</View>
</ImageBackground>);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchBox)
function mapStateToProps(state){
return {
postcode:state.postcode
}
}
function mapDispatchToProps(dispatch){
return{
setPostcode : (e) => dispatch({
type: 'SET_POSTCODE',
postcode : e
})
}
}
and finally relevant contentpage.js:
<View style={styles.container}>
<SearchBox navigation={this.props.navigation} eventTypeId={this.state.eventTypeId} />
<Image source={imageType(this.state.dataSource[0].eventTypeId)} style={styles.eventType} />
<Text style={styles.textToDisplay}>
{this.state.dataSource[0].eventTypeDescription}
</Text>
</View>
On load, the box is prepopulated with "abcefg" as expected. Changing the contents hits the reducer as expected. However when I navigate to a content page which loads the search box again the value is empty, regardless if I've changed the original state or not.
Am I missusing redux for what it's intended? Should I be doing this a different way?
For clarity below is the organisation of the components

In the reducer(), you are accessing action.text but in the dispatch(), you passed postcode value to postcode instead of text.

Related

React native navigation: '_this2.props.navigation.navigate'

my App.js code
import React from 'react'
import { Icon } from 'react-native-elements'
import { StyleSheet, View, StatusBar } from 'react-native'
import { createAppContainer } from 'react-navigation'
import { createStackNavigator } from 'react-navigation-stack'
import HomeScreen from './screens/homeScreen'
import LoginScreen from './screens/loginScreen'
import SignupScreen from './screens/signup'
import Screen2 from './screens/screen2'
import Screen1 from './screens/screen1'
export default class App extends React.Component {
setLogged(){
this.setState({logged:true})
this.forceUpdate()
}
//change to false when want to enable login feature else true
state = {
logged: false,
}
render() {
if(this.state.logged){
return(
<View style={styles.container} >
<StatusBar hidden = {false}/>
<AppContainer />
</View>
)
}else{
return(
<View style={styles.container} >
<StatusBar hidden = {false}/>
<LoginScreen signup={()=>this.props.navigation.navigate('Signup')} success={()=>this.setLogged()} />
</View>
)
}
}
}
const styles = StyleSheet.create({
container: {
position: 'relative',
width: '100%',
height: '100%',
backgroundColor: '#000000',
},
})
const HomeAppContainer = createStackNavigator(
{
Home: {screen: HomeScreen},
Signup: { screen: SignupScreen },
Screen1: { screen: Screen1 },
Screen2: { screen: Screen2 },
},
{initialRouteName: "Home"},
);
const AppContainer = createAppContainer(HomeAppContainer)
and the login screen contains
import React, { Component } from 'react';
import { StyleSheet, View, Text, TouchableOpacity, TextInput, ToastAndroid } from 'react-native';
import Colors from '../constants/colors'
import GLOBAL from '../constants/global'
export default class LoginScreen extends Component {
state = {
email: '',
password: '',
}
login() {
if (userid == 'admin') {
ToastAndroid.show('Invalid email/password', ToastAndroid.SHORT);
} else {
GLOBAL.userid = userid;
this.props.success()
}
})
}
render() {
return (
<View style={styles.MainContainer}>
<Text style={styles.text}>Email:</Text>
<View style={styles.inputView}>
<TextInput
style={styles.inputs}
autoFocus
returnKeyType="next"
keyboardType="email-address"
onChangeText={(email) => { this.setState({ email: email }) }}
/>
</View>
<Text style={styles.text}>Password:</Text>
<View style={styles.inputView}>
<TextInput
style={styles.inputs}
secureTextEntry
onChangeText={(password) => { this.setState({password:password}) }}
/>
</View>
<View style={styles.buttonGroup}>
<TouchableOpacity
style={styles.button}
onPress={() => { this.login() }} >
<Text style={{ fontSize: 24 }}>Sign in</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => { this.props.signup() }}>
<Text style={{ fontSize:24 }}>Sign up</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
//my styles here
});
the error is as follows
TypeError: undefined is not an object(evaluating '_this2.props.navigation.navigate')
i am learning react-native and making this login screen.
The app will check if the user is already logged in. if not then he will be prompted login screen which will have sign up option.
If the user is already logged in the app will directly go to home screen where screen1 and screen2 is used in stack
Well, the navigation prop is only passed down the navigation tree. Your App component is not a navigation screen like Home/Signup... (it doesn't have a route)... So basically App is not part of a navigator, it's not created using createStackNavigator or the others.
So, in conclusion the navigation prop is not defined.
Basically, how to fix this: LoginScreen should also be a route inside a navigator, therefore the navigation prop will be defined. Your App component should not render anything besides the <AppContainer /> and further logic should be handled in the first screen defined in the routes, which in your case might be the LoginScreen. In there you'll check if the user is logged in or not and navigate accordingly.
There's a great guide on their website on how to accomplish authentication flow: https://reactnavigation.org/docs/en/4.x/auth-flow.html

react navigation not rendering screens

I currently building a simple react native app which has two screens. I am using a react navigation to navigate between screens and when I tried following this guide https://reactnavigation.org/docs/en/hello-react-navigation.html, It doesn't render anything, just displaying a blank screen. Maybe I'm missing something that's why it doesn't render. Here's my js files:
App.js
import React, { Component } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import Login from './src/components/screens/login';
import Order from './src/components/screens/order';
const AppNavigator = createStackNavigator(
{
Login: Login,
Order: Order
},
{
initialRouteName: 'Login',
}
);
export default class Sales extends Component {
render() {
return (
<View>
<App />
</View>
);
}
}
login.js
class Login extends Components {
static navigationOptions = {
headerShown: false,
};
render() {
return(
<View>
<Button title="Login"
onPress={() => this.props.navigation.navigate('Order')}
/>
</View >
);
}
}
order.js
class Order extends Component {
static navigationOptions = {
headerShown: false,
};
render() {
return (
<View>
<View style={styles.button}>
<Button
title="Create Order"
color='#65639E'
/>
</View>
</View>
);
}
}
const AppNavigator = createStackNavigator(
{
Login: Login,
Order: Order
},
{
initialRouteName: 'Login',
}
);
export default class Sales extends Component {
render() {
return (
<View>
<App /> // this is worng.. As there is no app component. Never wrap
//AppNavigator inside a view
</View>
);
}
}
The right thing should be. Just trying to tempelate your requirement.
class Login extends React.Component {
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Login Screen</Text>
</View>
);
}
}
class Order extends React.Component {
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Order Screen</Text>
</View>
);
}
}
const AppNavigator = createStackNavigator({
Login: Login,
Order: Order
});
export default createAppContainer(AppNavigator);
I hope I'm able to make my point. If any confusion do mention in the comment. More than happy to help you out.
export default class Sales extends Component {
render() {
return (
<View>
<App /> // This code right here
</View>
);
}
}
You're rendering <App /> but you don't have an App Component. Maybe try using <Login />
try this
import React, { Component } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import Login from './src/components/screens/login';
import Order from './src/components/screens/order';
const AppNavigator = createStackNavigator(
{
Login: Login,
Order: Order
},
{
initialRouteName: 'Login',
}
);
class Sales extends Component {
render() {
return (
<View>
<AppNavigator /> --> pass here your navigation
</View>
);
}
}
export default createAppContainer(AppNavigator);

add onPress to first component and call it on second component

I'm a beginner in react-native I want to make add ( + ) button on the header of the first component when user click on onPress it will create a new form and the form is the second component
In App.js SetUp Navigation like below:
import { createAppContainer, createStackNavigator } from "react-navigation";
import MainScreen from "./screens/MainScreen";
import SecondScreen from "./screens/SecondScreen";
const AppNavigator = createStackNavigator({
MainScreen: {
screen: MainScreen
},
SecondScreen: {
screen: SecondScreen,
},
})
const App = createAppContainer(AppNavigator);
export default App;
In Main Screen
import React, { Component } from "react";
import {
View,
Button,
} from "react-native";
class MainScreen extends Component {
handleOnPress = () => {
this.props.navigation.navigate("SecondScreen");
};
render() {
return (
<View>
<Button
onPress={this.handleOnPress}
title="Button"
color="#841584"
/>
</View>
);
}
}
export default MainScreen;
It will redirect you to second screen
You can set ref of Form component
<FormsComponent
ref={ref => {
this.formComponent = ref;
}}
/>
You should pass function as prop for Header component
<HeaderComponent addNewForm={this.addNewForm} />
Parent component will call function of form component when click in header component using ref
addNewForm = () => {
this.formComponent.addNewForm();
};
App Preview
Complete Code
import React, { Component } from "react";
import { View, Text, FlatList } from "react-native";
class HeaderComponent extends Component {
render() {
return (
<View style={{ height: 80, backgroundColor: "red", paddingTop: 24 }}>
<Text style={{ padding: 20 }} onPress={this.props.addNewForm}>
Call Function of Form Component
</Text>
</View>
);
}
}
class FormsComponent extends Component {
addNewForm = () => {
alert("add new form");
};
render() {
return (
<FlatList
data={[{ key: "Form1" }, { key: "Form2" }]}
renderItem={({ item }) => <Text>{item.key}</Text>}
style={{ flex: 1 }}
/>
);
}
}
export default class App extends Component {
addNewForm = () => {
this.formComponent.addNewForm();
};
render() {
return (
<View style={{ flex: 1 }}>
<HeaderComponent addNewForm={this.addNewForm} />
<FormsComponent
ref={ref => {
this.formComponent = ref;
}}
/>
</View>
);
}
}

Go to detail page in a React-Native Flatlist

I made a Flatlist navigate to the detail screen when a row is clicked.
I created 4 files.
file1:
index.js
import React from 'react';
import List from "./list";
import Detail from "./detail";
import { createStackNavigator, createAppContainer } from "react-navigation";
const AppNavigator = createStackNavigator({
ListScreen: {
screen: List,
},
DetailScreen: {
screen: Detail,
},
}, {
initialRouteName: 'ListScreen',
});
export default createAppContainer(AppNavigator);
And detail.js
export default class DetailScreen extends React.PureComponent {
render() {
return (
<View
<Text>Home Details!</Text>
</View>
);
}
}
list.js
import Products from "./products";
export default class ListScreen extends React.PureComponent {
...
renderItem({ item }) {
return <Products product={item}/>
}
render() {
return (
<View style={{margin:5}}>
<FlatList
data={this.state.products}
renderItem={this.renderItem}
keyExtractor={(item,index) => item.id.toString()}
/>
</View>
);
}
And Finally
products.js
export default class ProductsType2 extends React.PureComponent {
_onPress = () => {
this.props.navigation.navigate('DetailScreen', this.props.product.id);
};
render() {
const { product} = this.props;
//const { navigate } = this.props.navigation; //get error w
return (
<Card>
<CardItem cardBody button onPress={this._onPress}>
<Image
style={{height: 140, width: 140, flex: 1}}
source={{uri: product.thumbnail}} />
</CardItem>
</Card>
);
}
}
when I press it I can't get the details screen to show up.
I get this error:
Cannot read property 'navigate' of underfined
use withNavigation (HOC) to access navigation props from products.js. That component not inside of the createStackNavigator, so export your class with withNavigation method and you can access the navigation props.
import { withNavigation } from 'react-navigation';
...
export default withNavigation(ProductsType2)
https://reactnavigation.org/docs/en/connecting-navigation-prop.html

Show logged in user info in react-native drawerNavigator

I've been searching for a simple solution with best practices to have drawer that would show user's info, i.e. name, age, etc. The login will happen in a separate screen but after the login is done, somehow user info should be passed to drawerNavigator.
DrawerNav
- StackNav
- Screen1
- Screen2
- SettingsScreen (login will happen here)
It's really frustrating that I couldn't find a working solution yet.
Expo code: https://snack.expo.io/#alisalimi25/user-info-drawer
import React, { Component } from 'react';
import {
Button,
SafeAreaView,
ScrollView,
Text,
View
} from 'react-native';
import {
createDrawerNavigator, createStackNavigator,
DrawerItems, NavigationActions
} from 'react-navigation';
class Screen2 extends React.Component {
render() {
return(
<View><Text>Screen2</Text></View>
);
}
}
class Screen1 extends React.Component {
render() {
return (
<SafeAreaView style={{ flex: 1 }}>
<Text>Screen1</Text>
<Button
onPress={() => this.props.navigation.openDrawer()}
title='Open Drawer' />
</SafeAreaView>
);
}
}
class SettingScreen extends React.Component {
loginUser = () => {
console.log('We need to pass user info into drawer navigator');
};
render() {
return (
<SafeAreaView>
<Text>Settings Page</Text>
<Button
onPress={() => this.loginUser()}
title='Login' />
</SafeAreaView>
);
}
}
const StackNav = createStackNavigator(
{
Screen1: Screen1,
Screen2: Screen2
}
);
const CustomDrawerContentComponent = (props) => {
return (
<ScrollView>
<SafeAreaView style={{ flex: 1 }} forceInset={{ top: 'always', horizontal: 'never' }}>
<Text>Hello USER_NAME_FROM_PROPS?</Text>
<DrawerItems {...props} />
</SafeAreaView>
</ScrollView>
)
};
const DrawerNav = createDrawerNavigator(
{
StackNav: {
screen: StackNav,
},
SettingScreen: {
screen: SettingScreen
}
},
{
contentComponent: CustomDrawerContentComponent
}
);
export default DrawerNav;
I'm sure the solution is somewhere but I couldn't find it yet. Any help is highly appreciated.
Thanks
So I managed to use screenProps to pass around parameters between pages but I'm not sure if it's a good pattern because it's like global variables and I think there is a chance of name collision between different layers of navigation. The working code is:
import React, { Component } from 'react';
import {
Button,
SafeAreaView,
ScrollView,
Text,
View
} from 'react-native';
import {
createDrawerNavigator, createStackNavigator,
DrawerItems, NavigationActions
} from 'react-navigation';
class Screen2 extends React.Component {
render() {
return(
<View><Text>Screen2</Text></View>
);
}
}
class Screen1 extends React.Component {
render() {
return (
<SafeAreaView style={{ flex: 1 }}>
<Text>Screen1</Text>
<Button
onPress={() => this.props.navigation.openDrawer()}
title='Open Drawer' />
</SafeAreaView>
);
}
}
class SettingScreen extends React.Component {
constructor(props) {
super(props);
console.log('passed props for settingScreen are: ', props);
}
loginUser = () => {
console.log('We need to pass user info into drawer navigator');
this.props.screenProps.userId = 'Gandalf';
};
render() {
return (
<SafeAreaView>
<Text>Settings Page</Text>
<Button
onPress={() => this.loginUser()}
title='Login' />
</SafeAreaView>
);
}
}
const StackNav = createStackNavigator(
{
Screen1: Screen1,
Screen2: Screen2
}
);
const CustomDrawerContentComponent = (props) => {
console.log('props in custom component are: ', props);
return (
<ScrollView>
<SafeAreaView style={{ flex: 1 }} forceInset={{ top: 'always', horizontal: 'never' }}>
<Text>Hello {props.screenProps.userId}</Text>
<DrawerItems {...props} />
</SafeAreaView>
</ScrollView>
)
};
const DrawerNav = createDrawerNavigator(
{
StackNav: {
screen: StackNav,
},
SettingScreen: {
screen: SettingScreen
}
},
{
contentComponent: CustomDrawerContentComponent
}
);
class DrawerNavWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedInUser: {
}
};
}
render() {
return(
<DrawerNav screenProps={this.state.loggedInUser} />
);
}
}
export default DrawerNavWrapper;
After "Login" button is pressed in SettingScreen, screenProps is set
this.props.screenProps.userId = 'Gandalf';
and when you open drawer, "Hello Gandalf" will be shown.
The other solution is to use JS Modules.
Or maybe use react context (https://github.com/react-navigation/react-navigation/issues/4511)
Anyone knows a better solution?
Thanks