React Redux is not updating the state - react-native

I am searching for a couple of days and I can't find out why redux is not updating the state I don't see any problem in my code please help me to find the problem.
it is a simple login project. I can see that data changes inside the reducer when debugging but it's not being mapped to props and state is not changing.
this is my code:
actions.js
import {
LOGIN_SUCCESS,
LOGIN_FAILURE,
} from './types'
export const loginSuccess = (user) => ({
type: LOGIN_SUCCESS,
payload: user
})
export const loginFailure = (error) => ({
type: LOGIN_FAILURE,
payload: error
})
loginReducer.js
import {
LOGIN_SUCCESS,
LOGIN_FAILURE,
} from './types'
const initialState = {
user: null,
errorMessage: null
}
export const loginReducer = (state = initialState, action) => {
switch (action.type) {
case LOGIN_SUCCESS:
return {...state,user: action.payload, errorMessage: null }
case LOGIN_FAILURE:
return {...state,errorMessage: action.payload }
default:
return state;
}
}
loginScreen.js
import React from 'react'
import {
Text,
View,
ImageBackground,
Button,
StyleSheet
} from 'react-native'
import { TextField } from 'react-native-material-textfield';
import { loginSuccess, loginFailure } from './reduxx/actions'
import { connect } from "react-redux";
class LoginScreen extends React.Component {
constructor(props) {
super(props)
this.state = {
email: "",
password: "",
}
}
_handlePress() {
this.props.login(this.state.email, this.state.password)
// user in propa is undifined
this.props.user
}
render() {
let { email, password } = this.state
return (
<ImageBackground source={require('./images/loginBackground.jpg')} style={styles.imageBackground}>
<View style={styles.mainContainer}>
<View style={styles.box}>
<TextField label="Email" value={email} onChangeText={email => this.setState({ email })} />
<TextField label="Password" textContentType="password" value={password} onChangeText={password => this.setState({ password })} />
<Button onPress={() => {
this._handlePress()
}} color="green" title="Sign in" style={styles.button} />
</View>
<View style={styles.bottomTextContainer}>
<Text style={{ color: "white" }}>Don't have an account?</Text>
<Text style={{ color: "lightgreen" }} onPress={() => this.props.navigation.navigate("Register")}> Sign up</Text>
</View>
</View>
</ImageBackground>
)
}
}
function mapStateToProps(state) {
return {
user: state.user,
errorMessage: state.errorMessage
}
}
function mapDispatchToProps(dispatch) {
return {
login: (email, password) => {
try {
let user = //get user from database
dispatch(loginSuccess(user))
} catch (error) {
dispatch(loginFailure(error))
}
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
const styles = StyleSheet.create({
imageBackground: {
width: '100%',
height: '100%'
},
mainContainer: {
flex: 1,
justifyContent: "center"
},
box: {
backgroundColor: 'white',
padding: 8,
margin: 8
},
button: {
margin: 10
},
bottomTextContainer: {
position: "absolute",
bottom: 8,
alignSelf: "center",
flexDirection: "row"
}
});
app.js
import React from 'react';
import { Provider } from "react-redux"
import store from "./reduxx/store";
import AppContainer from './Navigator'
export default class App extends React.Component {
render() {
return (
<Provider store={store} >
<AppContainer />
</Provider>
);
}
}
store.js
import {createStore} from "redux";
import { combineReducers } from 'redux';
import {loginReducer} from './loginReducer'
const rootReducer = combineReducers({
loginReducer,
});
export default store = createStore(rootReducer)

The state which comes into mapStateToProps contains all the combined reducers, so we need to access the reducer name first from the state (like state.loginReducer.user), before trying to access the data of that reducer.
PFB code which should work:
function mapStateToProps(state) {
return {
user: state.loginReducer.user,
errorMessage: state.loginReducer.errorMessage
}
}

Change here :
function mapStateToProps(state) {
return {
user: state.user,
errorMessage: state.errorMessage
}
}
TO
function mapStateToProps(state) {
return {
login: state.loginReducer
}
}
And then this.props.user to this.props.login.user and this.props.errorMessage to this.props.login.errorMessage in all the occurrence.

Related

The component for route '' must be a React Component

I'm new to react-native and am trying to use the Redux framework, I've encountered this problem: (The component for route ' ' must be a React component):
Thank you in advance.
Screen Error
Index.js
import React from 'react'
import { Provider } from 'react-redux'
import { AppRegistry } from 'react-native';
import { name as appName } from './app.json';
import Menu from './app/componnts/Menu'
import storeConfig from './app/store/storeConfig'
const store = storeConfig()
const Redux = () => {
return (
<Provider store={store}>
<Menu />
</Provider>
)
}
AppRegistry.registerComponent(appName, () => Redux);
Menu.js
import React, { Component } from 'react'
import {
StyleSheet,
SafeAreaView,
ScrollView,
Dimensions,
View,
Image,
Text
} from 'react-native'
import { createDrawerNavigator, DrawerItems, } from 'react-navigation'
import Login from './Login/Login'
import Viatura from './Viatura/Viatura'
export default class App extends Component {
render() {
return (
<AppDrawerNavigator />
)
}
}
const CustomDrawerComponent = (props) => (
<SafeAreaView style={{ flex: 1 }}>
<ScrollView>
<DrawerItems {...props} />
</ScrollView>
</SafeAreaView>
)
const AppDrawerNavigator = createDrawerNavigator({
Login,
Viatura,
}, {
contentComponent: CustomDrawerComponent,
drawerWidth: 300,
contentOptions: {
activeTintColor: 'orange'
}
})
Login.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { loginStore } from '../../store/actions/user.js';
import { Text, View, ImageBackground, Dimensions, Image, TextInput, TouchableOpacity, Alert } from 'react-native'
import Icon from 'react-native-vector-icons/FontAwesome'
import axios from 'axios'
import { createIconSetFromIcoMoon } from 'react-native-vector-icons'
import { API_MOTORISTAS_LOGIN } from '../../config'
import icoMoonConfig from '../../resources/fonts/selection.json'
import styles from './styles'
import bgImage from '../../images/fundo-vertical-preto.jpg'
import logo from '../../images/logo-tg-v-01.png'
const MyIcon = createIconSetFromIcoMoon(icoMoonConfig, 'icomoon', 'icomoon.ttf');
class Login extends Component {
constructor(props) {
super(props)
this.state = {
user: '',
password: '',
}
this.loginUser = this.loginUser.bind(this)
}
loginUser = () => {
try {
const { user, password } = this.state
this.setState({ error: '', })
axios.post(`${API_MOTORISTAS_LOGIN}`,
{
'param1': this.state.user,
'param2': this.state.password
}, {
"headers": {
'Content-Type': 'application/json',
}
}).then((response) => {
if (response.data.error) {
Alert.alert((response.data.error));
} else {
this.props.onLogin({ ...this.state })
const api_token = response.data.api_token
this.props.navigation.navigate('Viatura', {
api_token: api_token,
})
}
})
.catch((error) => {
console.log("axios error:", error);
});
} catch (err) {
Alert.alert((err))
}
}
static navigationOptions = {
drawerIcon: ({ tintColor }) => (
<MyIcon name="fonte-tg-33" style={{ fontSize: 24, color: tintColor }} />
)
}
render() {
return (
<ImageBackground
source={bgImage}
style={styles.backgroundContainer}>
<View
style={{
height: (Dimensions.get('window').height / 10) * 10,
alignItems: 'center',
justifyContent: 'center',
}}>
<View style={styles.logoContainer}>
<Image source={logo} style={styles.logo} />
</View>
<View style={styles.inputText}>
<View style={styles.iconContainer}>
<MyIcon style={styles.icon} name="fonte-tg-39" size={25} />
</View>
<TextInput
style={styles.input}
placeholder={'User'}
placeholderTextColor={'rgba(0, 0, 0, 0.6)'}
underlineColorAndroid='transparent'
value={this.state.user}
onChangeText={user => this.setState({ user })}
/>
</View>
<View style={styles.inputText}>
<View style={styles.iconContainer}>
<MyIcon style={styles.icon} name="fonte-tg-73" size={25} />
</View>
<TextInput
style={styles.input}
placeholder={'Password'}
secureTextEntry={true}
placeholderTextColor={'rgba(0, 0, 0, 0.6)'}
underlineColorAndroid='transparent'
value={this.state.password}
onChangeText={password => this.setState({ password })}
/>
</View>
<View style={styles.buttonsLogin}>
<TouchableOpacity style={styles.btnSair}>
<Text style={styles.text}> Sair </Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.loginUser} style={styles.btnEntrar}>
<Text style={styles.text}> Entrar </Text>
</TouchableOpacity>
</View>
</View>
</ImageBackground>
)
}
}
const mapDispatchToProps = dispatch => {
return {
onLogin: user => dispatch(loginStore(user))
}
}
export default connect(null, mapDispatchToProps)(Login);
actionTypes.js
export const USER_LOGGED_IN = 'USER_LOGGED_IN';
export const USER_LOGGED_OUT = 'USER_LOGGED_OUT';
user.js (inside actions folder)
import { USER_LOGGED_IN, USER_LOGGED_OUT } from './actionTypes'
export const login = user => {
return {
type: USER_LOGGED_IN,
payload: user
}
}
export const logout = () => {
return {
type: USER_LOGGED_OUT,
}
}
user.js (inside reducers folder)
import { USER_LOGGED_IN, USER_LOGGED_OUT } from '../actions/actionTypes'
const initalState = {
user: null,
password: null
}
const reducer = (state = initalState, action) => {
switch (action.type) {
case USER_LOGGED_IN:
return {
...state,
user: action.paypload.user,
password: action.paypload.password
}
case USER_LOGGED_OUT:
return {
...state,
user: null,
password: null,
}
default:
return state
}
}
export default reducer
storeConfig.js
import { createStore, combineReducers } from 'redux'
import userReducer from './reducers/user'
const reducers = combineReducers({
user: userReducer,
})
const storeConfig = () => {
return createStore(reducers)
}
export default storeConfig
As I said at the beginning of the post, I'm still a beginner at react-native, so I'm posting several snippets of code so that I can be as clear as possible about how my situation is.
I don't think your screen is properly setting export default.
can you change this code?
import React, { Component } from 'react'
import {
StyleSheet,
SafeAreaView,
ScrollView,
Dimensions,
View,
Image,
Text
} from 'react-native'
import { createDrawerNavigator, DrawerItems, } from 'react-navigation'
import Login from './Login/Login'
import Viatura from './Viatura/Viatura'
export default class App extends Component {
render() {
return (
<AppDrawerNavigator />
)
}
}
const CustomDrawerComponent = (props) => (
<SafeAreaView style={{ flex: 1 }}>
<ScrollView>
<DrawerItems {...props} />
</ScrollView>
</SafeAreaView>
)
const AppDrawerNavigator = createDrawerNavigator(
{
Login : () => <Login />,
Viatura : () => <Viatura />
},
{
contentComponent: CustomDrawerComponent,
drawerWidth: 300,
contentOptions: {
activeTintColor: 'orange'
}
}
)
OR
import { Login } from './Login/Login'
import { Viatura } from './Viatura/Viatura'
...
const AppDrawerNavigator = createDrawerNavigator(
{
Login : { screen: Login },
Viatura : { screen: Viatura }
},
{
contentComponent: CustomDrawerComponent,
drawerWidth: 300,
contentOptions: {
activeTintColor: 'orange'
}
}
)
Try this
const AppDrawerNavigator = createDrawerNavigator({
Login:Login,
Viatura:Login,
}, {
contentComponent: CustomDrawerComponent,
drawerWidth: 300,
contentOptions: {
activeTintColor: 'orange'
}
})

Check if function is dispatching action with jest and enzyme

I have a Login Screen that has a method that when called dispatches an action. I want to test using jest and enzyme if when the button is pressed, this function is called and, therefore, the action is dispatched. I tried in many different ways, but I couldn't achieve this.
screens/login.js
import React, { Component } from 'react';
import {
KeyboardAvoidingView,
TextInput,
StyleSheet,
Text,
Button,
TouchableOpacity
} from 'react-native';
import { connect } from 'react-redux';
import { login } from 'actions/sessions.js';
export class LoginScreen extends Component {
constructor(props){
super(props);
this.state = {
email: '',
password: '',
error: ''
}
}
requestLogin = async () => {
if(this.checkFields()){
this.setState({error: ''});
this.props.loginRequest(this.state.email, this.state.password);
}
}
checkFields = () => {
let { email, password } = this.state;
const re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if(!re.test(email)){
this.setState({error: 'E-mail must be valid'})
return false;
}else if(email == '' || password == ''){
this.setState({error: 'All fields are required!'});
return false
}else{
return true;
}
}
handleEmailText = (email) => {
this.setState({email})
}
handlePasswordText = (password) => {
this.setState({password})
}
render(){
return(
<KeyboardAvoidingView style={styles.container} enabled={true} behavior="padding">
<Text>{this.state.error || this.props.error}</Text>
<TextInput
onChangeText={(e) => this.handleEmailText(e)}
value={this.state.email}
keyboardType="email-address"
textContentType="emailAddress"
autoCapitalize="none"
placeholder="E-mail"
style={styles.input}
/>
<TextInput
onChangeText={(e) => this.handleEmailPassword(e)}
value={this.state.password}
placeholder="Password"
textContentType="password"
autoCapitalize="none"
secureTextEntry={true}
style={styles.input}
/>
<Button style={styles.button}
title="Sign In"
onPress={() => this.requestLogin()}/>
</KeyboardAvoidingView>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
input: {
width: "50%",
height: 40,
borderColor: 'gray',
borderWidth: 1,
paddingLeft: 20,
paddingRight: 20,
margin: 10
}
})
const mapDispatchToProps = dispatch => {
return {
loginRequest: (email, password) => dispatch(login(email, password))
}
}
const mapStateToProps = state => {
return {
error: state.sessions.error
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
tests/screens/login.test.js
import React from 'react';
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { LoginScreen } from 'screens/Login';
describe('LoginScreen', () => {
it('should dispatch login action when button clicked', async () => {
const mockStore = configureStore();
const initialState = {};
const store = mockStore(initialState);
const wrapper = shallow(<LoginScreen store={store}/>)
wrapper.setState({email: 'foo#bar.com', password: '1234'})
const component = await wrapper.dive();
component.find('button').simulate('click');
expect(store.getActions()).toMatchSnapshot();
});
})
When I have this approach, it says that simulate is meant to be run on 1 node. 0 found instead. I have no clue how to test it.
In react native you can use:
component.find('Button').props().onPress();
to simulate user interaction.
Also, you should use Button instead of button (your component name).

How to send the state of a Text Input variable to a reducer with dispatch and display it on another screen?

I want the state of my variable (with which it is given a value from a textInput) is sent to a reducer and change the state of that reducer by the state of the variable that I sent, so that way I can show it in different screens using mapStateToProps and I get it globally.
Is there any way to do that? I researched and found examples but not the way I want to do it.
I clarify my code is just an example so that you understand what I want to do, do not take it as a guide as I do not know if it works that way
I show you some of my code to give you an idea of ​​what I
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity
} from "react-native";
import { connect } from 'react-redux';
class ScreenHome extends Component {
static navigationOptions = {
header: null
}
constructor(props) {
super(props);
this.state = {
Texto: '',
}
}
render() {
this.props.ChangeState({type: 'ACTION_TYPE', Texto: this.state.Texto});
const { navigation } = this.props;
return (
<View style={styles.container}>
<TextInput
placeholder="Enter Text"
value={this.state.Texto}
onChangeText={Texto => this.setState({ Texto })}
/>
<View style={{ marginBottom: 10, marginTop: 10, backgroundColor: 'black', padding: 10 }}>
<TouchableOpacity onPress={this.props.ChangeState}>
<Text style={{ color: 'white' }}>Send Text Input status to the reducer</Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity style={{ backgroundColor: 'blue', padding: 10 }} onPress={() => { navigation.navigate('Other') }}>
<Text style={{ color: '#fff' }}>Go</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
// prop: state.prop
}
}
const mapDispatchToProps = (dispatch) => {
return {
ChangeState: () => {
// dispatch({ type: 'ACTION_TYPE', Texto: this.state.Texto });
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenHome)
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
OTHER SCREEN:
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity
} from "react-native";
import { connect } from 'react-redux';
class ScreenOther extends Component {
static navigationOptions = {
header: null
}
render() {
const { navigation } = this.props;
return (
<View style={styles.container}>
<Text>{this.props.StateInitial}</Text>
<TouchableOpacity onPress={() => { navigation.navigate('Home') }}>
<Text>Go back</Text>
</TouchableOpacity>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
StateInitial: state.reducerText
}
}
const mapDispatchToProps = (dispatch) => {
return {
// ChangeState: () => {
// dispatch({type: 'CHANGE_TEXT'})
// }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenOther)
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
Store
import { createStore, combineReducers } from 'redux';
const reducerText = (state = [0], action) => {
switch (action.type) {
case 'ACTION_TYPE':
return {...state, Texto:action.Texto};
default:
return state;
}
};
const Reducers = combineReducers({
reducerText
})
const Store = createStore(Reducers)
export default Store;
dispatch1 should be visible in your props in the homescreen. So if you do
this.props.dispatch1({type: 'YOUR_ACTION_TYPE', Text: this.state.Text});
Your reducer function will be called where you can do:
reducer: (state, action) => {
switch (action.type) {
case 'YOUR_ACTION_TYPE':
return {...state, Text:action.Text};
}
}
Then in the other screen you should get the changed Text prop.
For those who look at this post and want to do something similar, I mean send the status of the textInput variable to a reducer and ask for the status from another screen with redux feel free to see the code that I will leave below since I was investigating and I got it after a while.
This is the code of redux-form
import React, { Component } from "react";
import {
View,
TextInput,
StyleSheet,
Button,
Text
} from "react-native";
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
const ScreenFormHome = (props) => (
<View>
<Field name="Text" component={fieldNombre} ph="Enter Text" />
<Button title="Send Dispatch" onPress={props.handleSubmit((values) => props.SendDispatch(values))} />
</View>
);
const fieldNombre = (props) => (
<View style={styles.textInput}>
<TextInput
placeholder={props.ph}
onChangeText={props.input.onChange}
value={props.input.value}
autoCapitalize="none"
onBlur={props.input.onBlur}
/>
<View style={styles.linea} />
</View>
);
const styles = StyleSheet.create({
textInput: {
marginBottom: 16,
},
linea: {
backgroundColor: '#DCDCDC',
height: 2,
},
});
const mapDispatchToProps = (dispatch) => {
return {
SendDispatch: (values) => {
dispatch({ type: 'ACTION_TYPE', Text: values.Text })
}
}
}
const mapStateToProps = (state) => {
return {
// StateInitial: state.reducerText
}
}
export default reduxForm({
form: 'ScreenFormHome',
})(connect(mapStateToProps, mapDispatchToProps)(ScreenFormHome));
and this is the component code
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity
} from "react-native";
import ScreenFormHome from "./ScreenFormHome";
class ScreenHome extends Component {
static navigationOptions = {
header: null
}
render() {
const { navigation } = this.props;
return (
<View style={styles.container}>
<TouchableOpacity style={{ backgroundColor: 'blue', padding: 10, marginBottom: 10 }} onPress={() => { navigation.navigate('Other') }}>
<Text style={{ color: '#fff' }}>Go</Text>
</TouchableOpacity>
<ScreenFormHome />
</View>
);
}
}
export default ScreenHome;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
This is the Store code
import { createStore, combineReducers } from 'redux';
import { reducer as form } from 'redux-form'
const reducerText = (state = [], action) => {
switch (action.type) {
case 'ACTION_TYPE':
return (action.Text)
default:
return state;
}
};
const Reducers = combineReducers({
reducerText,
form
})
const Store = createStore(Reducers)
export default Store;

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

I'm currently developing an app using react native, right now my issue is that i couldn't navigate to main screen after login. Below is my code.
This is App.js (EDITED)
import React from 'react';
import { Loading } from './components/common/';
import TabNavigator from './screens/TabNavigator';
import AuthNavigator from './screens/AuthNavigator';
import MainNavigator from './screens/MainNavigator';
import deviceStorage from './services/deviceStorage.js';
import { View, StyleSheet } from 'react-native';
export default class App extends React.Component {
constructor() {
super();
this.state = {
token: '',
loading: true
}
this.newJWT = this.newJWT.bind(this);
this.deleteJWT = deviceStorage.deleteJWT.bind(this);
this.loadJWT = deviceStorage.loadJWT.bind(this);
this.loadJWT();
}
state = {
isLoadingComplete: false,
};
newJWT(token){
this.setState({
token: token
});
}
render() {
if (this.state.loading) {
return (
<Loading size={'large'} />
);
} else if (!this.state.token) {
return (
<View style={styles.container}>
<AuthNavigator screenProps = {{setToken:this.newJWT}} />
</View>
);
} else if (this.state.token) {
return (
<View style={styles.container}>
<MainNavigator screenProps = {{token: this.state.token,
deleteJWT:this.deleteJWT,}} />
</View>
);
}
}
}
This is Login.js (EDITED-v2)
import React, { Component, Fragment } from 'react';
import { Text, View, StyleSheet, ImageBackground, KeyboardAvoidingView,
TouchableOpacity, TextInput, Alert } from 'react-native';
import axios from 'axios';
import deviceStorage from '../services/deviceStorage';
class Login extends Component {
constructor(props) {
super(props)
this.state = {
username: '',
password: '',
error: '',
loading: false
};
this.loginUser = this.loginUser.bind(this);
this.onLoginFail = this.onLoginFail.bind(this);
}
loginUser() {
const { username, password, password_confirmation } = this.state;
this.setState({ error: '', loading: true });
// NOTE Post to HTTPS only in production
axios.post("http://192.168.1.201:8000/api/login",{
username: username,
password: password
})
.then((response) => {
console.log('response',response)
deviceStorage.saveKey("token", response.data.token);
console.log(response.data.token);
this.props.newJWT(response.data.token);
})
.catch((error) => {
const status = error.response.status
if (status === 401) {
this.setState({ error: 'username or password not recognised.' });
}
this.onLoginFail();
//console.log(error);
//this.onLoginFail();
});
}
onLoginFail() {
this.setState({
error: 'Login Failed',
loading: false
});
}
render() {
// other codes here
}
const styles = StyleSheet.create({
// other codes here
});
export { Login };
This is TabNavigator.js (Added)
import React from 'react';
import { Text } from 'react-native';
import { createMaterialTopTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import Profile from '../screens/Profile';
const TabNavigator = createMaterialTopTabNavigator(
{
Profile: {
screen: props => <Profile {...props.screenProps} />,
navigationOptions: {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-person' : 'ios-person'} //TODO change to focused
icon
size={30}
style={{ color: tintColor }}
/>
),
}
},
},
{ initialRouteName: 'Profile',
tabBarPosition: 'top',
swipeEnabled: false,
animationEnabled: true,
lazy: true,
tabBarOptions: {
showLabel: false,
showIcon: true,
activeTintColor: 'orange',
inactiveTintColor: 'orange',
style: {
backgroundColor: '#555',
},
indicatorStyle: {
color: '#orange'
}
}
}
);
const screenTitles = {
Profile: { title: 'Profiler' },
Home: { title: 'Home' },
};
TabNavigator.navigationOptions = ({ navigation }) => {
const { routeName } = navigation.state.routes[navigation.state.index];
const headerTitle = screenTitles[routeName].title;
const tabBarVisible = false;
return {
headerTitle,
tabBarVisible
};
};
export default TabNavigator;
This is my AuthLoadingScreen.js
import React from 'react';
import { View } from 'react-native';
import { Login } from '../screens/Login';
class AuthLoadingScreen extends React.Component {
constructor(props){
super(props);
this.state = {
showLogin: true
};
this.whichForm = this.whichForm.bind(this);
this.authSwitch = this.authSwitch.bind(this);
}
authSwitch() {
this.setState({
showLogin: !this.state.showLogin
});
}
whichForm() {
if(this.state.showLogin){
return(
<Login newJWT={this.props.newJWT} authSwitch={this.authSwitch} />
);
} else {
}
}
render() {
return(
<View style={styles.container}>
{this.whichForm()}
</View>
);
}
}
export default AuthLoadingScreen;
const styles = {
// style codes here
};
Lastly, this is my Profile.js
import React, { Component } from 'react';
import { View, Text, TouchableOpacity, Alert, Platform } from
'react-native';
import { Button, Loading } from '../components/common/';
import axios from 'axios';
export default class Profile extends Component {
constructor(props){
super(props);
this.state = {
loading: true,
email: '',
name: '',
error: ''
}
}
componentDidMount(){
this.onLocationPressed();
const headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + this.props.token
};
axios({
method: 'GET',
url: 'http://192.168.1.201:8000/api/user',
headers: headers,
}).then((response) => {
console.log('response',response)
console.log('response2',this.props.token)
this.setState({
email: response.data.user.email,
name: response.data.user.name,
loading: false
});
}).catch((error) => {
console.log(error);
this.setState({
error: 'Error retrieving data',
loading: false
});
});
}
render() {
const { container, emailText, errorText } = styles;
const { loading, email, name, error } = this.state;
if (loading){
return(
<View style={container}>
<Loading size={'large'} />
</View>
)
} else {
return(
<View style={container}>
<View>
<Text style={emailText}>Your email: {email}</Text>
<Text style={emailText}>Your name: {name}</Text>
</View>
<Button onPress={this.props.deleteJWT}>
Log Out
</Button>
</View>
);
}
}
}
const styles = {
// style codes here
};
I've fixed the previous problem that couldn't start the app. Right now i can see the login screen, but when i pressed login, there's a yellow box that indicates some problem. I've included the screenshot below.
Lastly i've added the deviceStorage.js
deviceStorage.js
import { AsyncStorage } from 'react-native';
const deviceStorage = {
async saveKey(key, valueToSave) {
try {
await AsyncStorage.setItem(key, valueToSave);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
},
async loadJWT() {
try {
const value = await AsyncStorage.getItem('token');
if (value !== null) {
this.setState({
token: value,
loading: false
});
} else {
this.setState({
loading: false
});
}
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
},
async deleteJWT() {
try{
await AsyncStorage.removeItem('token')
.then(
() => {
this.setState({
token: ''
})
}
);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
}
};
export default deviceStorage;
Before navigate
After navigate
This is my setup. It works like a charm. Sorry if it's a bit messy. I removed some stuff for clarity and I may have missed something:
App.js
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { Asset, Font, Icon } from 'expo';
import { ENDPOINT, USER_TYPE } from './src/config'
import { Loading } from './src/components/common/';
import deviceStorage from './src/services/deviceStorage.js';
import TabNavigator from './src/TabNavigator';
import AuthNavigator from './src/AuthNavigator';
import MainNavigator from './src/MainNavigator';
import globalStyles from './src/globalStyles';
import './ReactotronConfig';
export default class App extends React.Component {
constructor() {
super();
this.state = {
jwt: '',
loading: true,
};
this.newJWT = this.newJWT.bind(this);
this.deleteJWT = deviceStorage.deleteJWT.bind(this);
this.loadJWT = deviceStorage.loadJWT.bind(this);
this.loadJWT();
}
state = {
isLoadingComplete: false,
};
newJWT(jwt) {
this.setState({
jwt: jwt
});
}
render() {
if (this.state.loading) {
return (
<Loading size={'large'} />
);
} else if (!this.state.jwt) {
//console.log(this.props, '<=== app.js');
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AuthNavigator screenProps={{setToken: this.newJWT }} />
</View>
);
} else {
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<MainNavigator
screenProps={{ jwt: this.state.jwt,
deleteToken: this.deleteJWT,
}}
/>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
justifyContent: 'center',
},
});
AuthNavigator.js
import React from 'react';
import { createAppContainer, createBottomTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import AuthScreen from './screens/AuthScreen';
const AuthNavigator = createBottomTabNavigator(
{
Auth: (props) => {
return <AuthScreen {...props.screenProps} />;
}
},
{ initialRouteName: 'Auth',
tabBarOptions: {
showLabel: false,
activeBackgroundColor: '#eee',
}
}
);
export default createAppContainer(AuthNavigator);
MainNavigator.js
import React from 'react';
import { createStackNavigator, createAppContainer } from 'react-navigation';
import TabNavigator from './TabNavigator';
const MainNavigator = createStackNavigator({
Main: TabNavigator },
{
initialRouteName: 'Main',
defaultNavigationOptions: {
headerTitleStyle: {
fontSize: 20,
textTransform: 'uppercase'
}
}
});
export default createAppContainer(MainNavigator);
TabNavigator.js
import React from 'react';
import { Text } from 'react-native';
import { createMaterialTopTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import IconBadge from 'react-native-icon-badge';
import ProfileScreen from './screens/ProfileScreen';
import NotificationsScreen from './screens/NotificationsScreen';
import HomeStackNavigator from './HomeStackNavigator';
import CartStackNavigator from './CartStackNavigator';
import QuotesStackNavigator from './QuotesStackNavigator';
import InitialRoute from './InitialRoute';
const TabNavigator = createMaterialTopTabNavigator(
{
Profile: {
screen: props => <ProfileScreen {...props.screenProps} />,
navigationOptions: {
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-person' : 'ios-person'} //TODO change to focused icon
size={30}
style={{ color: tintColor }}
/>
),
}
},
Home: HomeStackNavigator,
Quotes: QuotesStackNavigator,
Notifications: { screen: props => <NotificationsScreen {...props.screenProps} />,
navigationOptions: ({ screenProps }) => ({
tabBarIcon: ({ tintColor, focused }) => (
<IconBadge
MainElement={
<Ionicons
name={focused ? 'ios-notifications' : 'ios-notifications'}
size={30}
style={{ color: tintColor }}
/>
}
BadgeElement={
<Text style={{ color: '#FFFFFF' }}>{screenProps.unreadMessagesCount}</Text>
}
IconBadgeStyle={{ width: 15,
height: 15,
position: 'absolute',
top: 1,
left: -6,
marginLeft: 15,
backgroundColor: 'red' }}
Hidden={screenProps.unreadMessagesCount === 0}
/>
)
})
},
Cart: CartStackNavigator,
},
{ initialRouteName: 'Profile',
tabBarPosition: 'top',
swipeEnabled: false,
animationEnabled: true,
lazy: true,
tabBarOptions: {
showLabel: false,
showIcon: true,
activeTintColor: 'orange',
inactiveTintColor: 'orange',
style: {
backgroundColor: '#555',
},
indicatorStyle: {
color: '#orange'
}
}
}
);
const screenTitles = {
Profile: { title: 'Hola Maestro' },
Home: { title: 'Selecciona la Categoría' },
Quotes: { title: 'Mi Historial de Cotizaciones' },
Notifications: { title: 'Notificaciones' },
Cart: { title: 'Mi Pedido' },
};
TabNavigator.navigationOptions = ({ navigation }) => {
const { routeName } = navigation.state.routes[navigation.state.index];
const headerTitle = screenTitles[routeName].title;
const tabBarVisible = false;
return {
headerTitle,
tabBarVisible
};
};
export default TabNavigator;
Login.js
import React, { Component, Fragment } from 'react';
import { Text, View, StyleSheet, ImageBackground, KeyboardAvoidingView } from 'react-native';
import axios from 'axios';
import Ionicons from 'react-native-vector-icons/Ionicons';
//import Pusher from 'pusher-js/react-native';
import { ENDPOINT, USER_TYPE } from '../config'
import deviceStorage from '../services/deviceStorage';
import { Input, TextLink, Loading, Button } from './common';
import Header from '../components/Header';
class Login extends Component {
constructor(props){
super(props);
this.state = {
username: '',
password: '',
error: '',
loading: false
};
this.pusher = null; // variable for storing the Pusher reference
this.my_channel = null; // variable for storing the channel assigned to this user
this.loginUser = this.loginUser.bind(this);
}
loginUser() {
const { username, password, password_confirmation } = this.state;
axios.post(`${ENDPOINT}/login`, {
user: {
login: username,
password: password
}
})
.then((response) => {
deviceStorage.saveKey("id_token", response.headers.authorization);
this.props.newJWT(response.headers.authorization);
//this.setPusherData();
})
.catch((error) => {
this.onLoginFail();
});
}
onLoginFail() {
this.setState({
error: 'Usuario o contraseña incorrecta',
loading: false
});
}
}
render() {
const { username, password, error, loading } = this.state;
const { container, form, section, errorTextStyle, iconContainer, inputContainer, titleText } = styles;
return (
<View style={container}>
<Header title="¡Bienvenido Amigo Maestro!" />
<View style={form}>
<ImageBackground source={require('./cemento-login.jpg')} style={{ flex: 1, marginBottom: 30 }}>
<View style={{marginTop: 120}}>
<Text style={titleText}>INICIAR SESIÓN</Text>
<View style={section}>
<View style={iconContainer}>
<Ionicons
name={'ios-person'}
size={26}
style={{ color: '#fff', alignSelf: 'center' }}
/>
</View>
<View style={inputContainer}>
<Input
placeholder="Usuario"
value={username}
onChangeText={username => this.setState({ username })}
/>
</View>
</View>
<View style={section}>
<View style={iconContainer}>
<Ionicons
name={'ios-lock'}
size={26}
style={{ color: '#fff', alignSelf: 'center' }}
/>
</View>
<View style={inputContainer}>
<Input
secureTextEntry
placeholder="Contraseña"
value={password}
onChangeText={password => this.setState({ password })}
/>
</View>
</View>
</View>
</ImageBackground>
</View>
<KeyboardAvoidingView
behavior="padding"
keyboardVerticalOffset={30}
>
<TextLink style={{ }} onPress={this.props.formSwitch}>
Aún no estas registrado? Regístrate
</TextLink>
<TextLink style={{ }} onPress={this.props.forgotPassword}>
Olvidaste tu contraseña?
</TextLink>
<Text style={errorTextStyle}>
{error}
</Text>
{!loading ?
<Button onPress={this.loginUser}>
Ingresar
</Button>
:
<Loading size={'large'} />}
</KeyboardAvoidingView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
form: {
flex: 0.8
},
section: {
flexDirection: 'row',
backgroundColor: '#eee',
borderRadius: 3,
marginTop: 10,
height: 40,
marginLeft: '10%',
marginRight: '10%',
},
titleText: {
color: '#fff',
alignSelf: 'center',
fontSize: 20,
marginBottom: 10
},
errorTextStyle: {
alignSelf: 'center',
fontSize: 18,
color: 'red'
},
iconContainer: {
flex: 0.1,
height: 40,
borderRadius: 3,
alignSelf: 'center',
justifyContent: 'center',
backgroundColor: 'orange',
},
inputContainer: {
flex: 0.8,
alignSelf: 'flex-start',
marginLeft: -70,
}
});
export { Login };
Inside App.js you never change loading to false so the render method never gets to any of the other conditions. Once you have received the token you need to update state and change loading.

React Native Emptying Props

I have a screen that includes Signin and Signup buttons. When a user taps the SigninButton, I send him to login screen. In the login screen, there are two text inputs which receive email and password from user. If it is successful, user sent to main screen. If not, I throw an error text. But if user go back from navigation bar and come back to the login screen, email input and error message is still showing there. I am using redux, I can not empty the props that hold email and error text. I tried componentWillUnmount, componentWillMount etc. but still can't find the right place to empty these props. This is the code in my login screen;
class LoginScreen extends Component {
componentWillReceiveProps(nextProps) {
this.onAuthComplete(nextProps);
}
onAuthComplete(props) {
if (props.user) {
this.props.navigation.navigate('main');
}
}
render() {
return(
<View style={styles.container}>
<SigninForm />
</View>
);
}
}
const styles = StyleSheet.create({
........
}
});
const mapStateToProps = ({ auth }) => {
const { user, email, error } = auth;
return { user, email, error };
}
export default connect(mapStateToProps, { emailChanged, loginUser })(LoginScreen);
and also this is the code in the reducer;
import ......
const INITIAL_STATE = {
email: '',
password: '',
user: null,
error: '',
};
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, error: ''};
case LOGIN_USER_SUCCESS:
return {...state, ...INITIAL_STATE, user: action.payload};
case LOGIN_USER_FAIL:
return {...state, error: 'Authentication Failed', password: ''};
default:
return state;
}
};
And here is the SigninForm code;
import React, { Component } from 'react';
import { View, Dimensions, Text } from 'react-native';
import { connect } from 'react-redux';
import { FormInput, FormLabel } from 'react-native-elements';
import { AuthButton } from './';
import { emailChanged, passwordChanged, loginUser } from '../actions';
const SCREEN_WIDTH = Dimensions.get('window').width;
const SCREEN_HEIGHT = Dimensions.get('window').height;
class SigninForm extends Component {
onEmailChange(text) {
this.props.emailChanged(text);
}
onPasswordChange(text) {
this.props.passwordChanged(text);
}
onButtonPress() {
const { email, password } = this.props;
this.props.loginUser({ email, password });
}
render() {
return(
<View style={styles.viewStyle}>
<FormLabel labelStyle={styles.labelStyle}>Email</FormLabel>
<FormInput
placeholder='Enter your email'
keyboardType="email-address"
containerStyle={styles.containerStyle}
inputStyle={styles.inputStyle}
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
/>
<FormLabel labelStyle={styles.labelStyle}>Password</FormLabel>
<FormInput
placeholder='Enter a password'
secureTextEntry={true}
containerStyle={styles.containerStyle}
inputStyle={styles.inputStyle}
onChangeText={this.onPasswordChange.bind(this)}
value={this.props.password}
/>
<Text style={styles.errorTextStyle}>
{this.props.error}
</Text>
<AuthButton
title='Sign In'
backgroundColor='#eb4454'
onPress={this.onButtonPress.bind(this)}
/>
</View>
);
}
}
const styles = {
viewStyle: {
top: SCREEN_HEIGHT * -0.15
},
containerStyle: {
marginBottom: 10,
width: SCREEN_WIDTH * 0.80,
borderBottomColor: '#3c143c'
},
labelStyle: {
color: '#3c143c',
fontFamily: 'System',
fontSize: 20
},
inputStyle: {
color: '#3c143c',
fontFamily: 'System',
fontSize: 20
},
errorTextStyle: {
fontSize: 20,
alignSelf: 'center',
color: 'red'
}
}
const mapStateToProps = ({ auth }) => {
const { email, password, error } = auth;
return { email, password, error };
}
export default connect(mapStateToProps, { emailChanged, passwordChanged, loginUser })(SigninForm);
I did it in componentWillMount method. I called an action to trigger a reducer to update states of the relevant props. Here is code snippet in my component;
import React, { Component } from 'react';
import { View, Dimensions, Text } from 'react-native';
import { connect } from 'react-redux';
import { FormInput, FormLabel } from 'react-native-elements';
import { AuthButton } from './';
import { emailChanged, passwordChanged, loginUser, emptyInput } from '../actions';
const SCREEN_WIDTH = Dimensions.get('window').width;
const SCREEN_HEIGHT = Dimensions.get('window').height;
class SigninForm extends Component {
componentWillMount() {
//used for empty email and error text when user go back and forth
const { email, error } = this.props;
this.props.emptyInput(email, error);
}
onEmailChange(text) {
this.props.emailChanged(text);
}
onPasswordChange(text) {
this.props.passwordChanged(text);
}
onButtonPress() {
const { email, password } = this.props;
this.props.loginUser({ email, password });
}
render() {
console.log("render ediyorum");
return(
<View style={styles.viewStyle}>
<FormLabel labelStyle={styles.labelStyle}>Email</FormLabel>
<FormInput
placeholder='Enter your email'
keyboardType="email-address"
containerStyle={styles.containerStyle}
inputStyle={styles.inputStyle}
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
/>
<FormLabel labelStyle={styles.labelStyle}>Password</FormLabel>
<FormInput
placeholder='Enter a password'
secureTextEntry={true}
containerStyle={styles.containerStyle}
inputStyle={styles.inputStyle}
onChangeText={this.onPasswordChange.bind(this)}
value={this.props.password}
/>
<Text style={styles.errorTextStyle}>
{this.props.error}
</Text>
<AuthButton
title='Sign In'
backgroundColor='#eb4454'
onPress={this.onButtonPress.bind(this)}
/>
</View>
);
}
}
const styles = {
viewStyle: {
top: SCREEN_HEIGHT * -0.15
},
containerStyle: {
marginBottom: 10,
width: SCREEN_WIDTH * 0.80,
borderBottomColor: '#3c143c'
},
labelStyle: {
color: '#3c143c',
fontFamily: 'System',
fontSize: 20
},
inputStyle: {
color: '#3c143c',
fontFamily: 'System',
fontSize: 20
},
errorTextStyle: {
fontSize: 20,
alignSelf: 'center',
color: 'red'
}
}
const mapStateToProps = ({ auth }) => {
const { email, password, error } = auth;
return { email, password, error };
}
export default connect(mapStateToProps, { emailChanged, passwordChanged, loginUser, emptyInput })(SigninForm);
This is emptyInput action creator code;
export const emptyInput = (email, error) => {
return(dispatch) => {
dispatch({ type: EMPTY_INPUT });
}
};
And finally the reducer code doing the real job;
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
CREATE_USER,
CREATE_USER_SUCCESS,
CREATE_USER_FAIL,
LOGIN_USER,
LOGIN_USER_FAIL,
LOGIN_USER_SUCCESS,
EMPTY_INPUT
} from '../actions/types';
const INITIAL_STATE = {
email: '',
password: '',
user: null,
error: '',
};
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 CREATE_USER:
return {...state, error: '' };
case CREATE_USER_SUCCESS:
return {...state, ...INITIAL_STATE, user: action.payload};
case CREATE_USER_FAIL:
return {...state, error: 'Authentication Failed!', password: ''};
case LOGIN_USER:
return {...state, error: ''};
case LOGIN_USER_SUCCESS:
return {...state, ...INITIAL_STATE, user: action.payload};
case LOGIN_USER_FAIL:
return {...state, error: 'Authentication Failed', password: ''};
case EMPTY_INPUT:
return {...state, ...INITIAL_STATE};
default:
return state;
}
};