React Native + Redux component renders before store action's complete - react-native

I have react native application with redux, where user get nevigated to home component after successful login. But home component get rendered before it receive user profile through store. If I use 'Home' component as connected component then on re-render it receives profile.
It is a correct flow or do I able to delay rendering of 'Home' till store is populated with new data.
Here is code
Types
export const FETCH_PROFILE = 'FETCH_PROFILE';
export const UPDATE_PROFILE = 'UPDATE_PROFILE';
export const DELETE_PROFILE = 'DELETE_PROFILE';
export const FETCH_STREAMS = 'FETCH_STREAMS';
Reducer
export default function profile(state = {}, action) {
switch (action.type) {
case types.FETCH_PROFILE:
return {
...state,
profile: action.profile
}
case types.UPDATE_PROFILE:
return {
...state,
profile: action.profile
}
case types.DELETE_PROFILE:
return {
...state,
profile: null
};
default:
return state;
}
}
Actions
var PROFILE_KEY = "#myApp:profile";
export function fetchProfile() {
return dispatch => {
AsyncStorage.getItem(PROFILE_KEY)
.then((profileString) => {
dispatch({
type: types.FETCH_PROFILE,
profile: profileString ? JSON.parse(profileString) : {}
})
})
}
}
export function updateProfile(data) {
return dispatch => {
AsyncStorage.setItem(PROFILE_KEY, JSON.stringify(data))
.then(() => {
dispatch({
type: types.UPDATE_PROFILE,
profile: data
})
})
}
}
export function deleteProfile() {
return dispatch => {
AsyncStorage.removeItem(PROFILE_KEY)
.then(() => {
dispatch({
type: types.DELETE_PROFILE
})
})
}
}
Login Component
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
error: "",
showProgress: false,
};
}
_focusNextField(nextField) {
this.refs[nextField].focus();
}
_onLoginPressed() {
this.setState({showProgress: true});
this._login();
}
async _login() {
try {
let response = await fetch( BASE_URL + url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
user: {
email: this.state.username,
password: this.state.password,
}
})
});
let res = await response.text();
if (response.status >= 200 && response.status < 300) {
let user = JSON.parse(res);
this.props.updateProfile(user.user);
this.setState({showProgress: false});
this.props.navigator.replace({name: 'Home'});
}
else {
let error = JSON.parse(res);
throw error.errors;
}
} catch(error) {
this.setState({error: error});
this.setState({showProgress: false});
console.log("error " + error);
}
}
render() {
return (
<View style={styles.loginBox}>
<TextInput
ref="username"
value={this.state.username}
placeholder="Username"
keyboardType="email-address"
onChangeText={(username) => this.setState({username}) }
onSubmitEditing={() => this._focusNextField('password')}/>
<TextInput
ref="password"
placeholder="Password"
value={this.state.password}
secureTextEntry={true}
onChangeText={(password) => this.setState({password}) }
returnKeyType="go"/>
<Button textStyle={{fontSize: 14}} onPress={this._onLoginPressed.bind(this)} style={{marginTop: 30}}>
Sign In
</Button>
</View>
);
}
}
const styles = StyleSheet.create({
loginBox: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
alignItems: 'stretch',
margin: 10,
}
});
var {updateProfile} = require('../Actions');
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
module.exports = connect(
null,
(dispatch) => {
return bindActionCreators({updateProfile}, dispatch)
}
)(Login)
Home
class Home extends React.Component {
render() {
return (
<View style={{flex: 1, backgroundColor: '#fff'}}>
<Text style={{margin: 10, fontSize: 15, textAlign: 'left'}}>I'm in the Drawer!</Text>
<Text>Auth key : {this.props.profile ? this.props.profile.authentication_token : 'authentication_token'}</Text>
</View>
);
}
}
//module.exports = Home;
import { connect } from 'react-redux';
module.exports = connect(
(state) => {
return {
profile: state.profile
}
},
null
)(Home)

If you're using redux-thunk, you can delay the transition until data is loaded. You need to change some small things.
Add return to action creator.
export function updateProfile(data) {
return dispatch => {
return AsyncStorage.setItem(PROFILE_KEY, JSON.stringify(data))
.then(() => {
dispatch({
type: types.UPDATE_PROFILE,
profile: data
})
})
}
}
add await
if (response.status >= 200 && response.status < 300) {
let user = JSON.parse(res);
await this.props.updateProfile(user.user);
this.setState({showProgress: false});
this.props.navigator.replace({name: 'Home'});
}

Related

Undefined is not a function {near This.state} react Native error

In my react native code I am facing this issue. some time it's work and some time it doesn't.
I am beginner in user of React-redux for state management of component any lead will be appreciated.
import React, { PureComponent } from "react"
import PurchaseInvoiceView from "../Components/Purchaseinvoiceview"
import ReturnInvoiceView from "../Components/Returninvoiceview"
import { connect } from "react-redux"
import { translate } from "../i18n"
import { TabView, SceneMap, TabBar } from "react-native-tab-view"
import styles from "./Styles/InvoiceScreenStyle"
import { Cache } from "react-native-cache"
import AsyncStorage from "#react-native-community/async-storage";
import {
getPurchaseInvoice,
getPurchaseInvoiceFromCache,
getReturnInvoice,
getReturnInvoiceFromCache,
} from "../Redux/Actions/invoiceActions"
class InvoiceScreen extends PureComponent {
state = {
index: 0,
routes: [
{ key: "purchase", title: translate("invoiceScreen.purchase") },
{ key: "return", title: translate("invoiceScreen.return") },
],
token: null,
vansale_id: 0,
user: null,
}
componentDidMount() {
const { dispatch, userData } = this.props
this.setState(
{
token: userData.data.package.token || value.package.token,
vansale_id: userData.data.package.id || value.package.id,
},
() => {
const { token, vansale_id } = this.state
dispatch(getPurchaseInvoice(token, { vansale_id}))
.then(res => {
console.log(res);
if (res.payload.status === true) {
var cache = new Cache({
namespace: "FGMM",
policy: {
maxEntries: 50000,
},
backend: AsyncStorage,
})
cache.setItem("invoiceData", res.payload, function(err) {})
}
})
.catch(err => {
var cache = new Cache({
namespace: "FGMM",
policy: {
maxEntries: 50000,
},
backend: AsyncStorage,
})
cache.getItem("invoiceData", function(err, value) {
if (value) {
if (value.status === true) {
dispatch(getPurchaseInvoiceFromCache(value))
}
}
})
})
dispatch(getReturnInvoice(token, { vansale_id,}))
.then(res => {
console.log(res)
if (res.payload.status === true) {
var cache = new Cache({
namespace: "FGMM",
policy: {
maxEntries: 50000,
},
backend: AsyncStorage,
})
cache.setItem("invoiceReturnData", res.payload, function(err) {})
}
})
.catch(err => {
var cache = new Cache({
namespace: "FGMM",
policy: {
maxEntries: 50000,
},
backend: AsyncStorage,
})
cache.getItem("invoiceReturnData", function(err, value) {
if (value) {
if (value.status === true) {
dispatch(getReturnInvoiceFromCache(value))
}
}
})
})
},
)
var cache = new Cache({
namespace: "FGMM",
policy: {
maxEntries: 50000,
},
backend: AsyncStorage,
})
cache.getItem("userData", function(err, value) {
if (value) {
if (value.status === true) {
}
} else {
this.setState({
token: userData.data.package.token || value.package.token,
vansale_id: userData.data.package.id || value.package.id,
})
}
})
}
render() {
return (
<View style={styles.container}>
<TabView
navigationState={this.state}
activeColor="#777"
inactiveColor="#000"
renderTabBar={props => (
<TabBar
{...props}
indicatorStyle={styles.indicator}
style={styles.tabBar}
labelStyle={styles.labelStyle}
/>
)}
renderScene={SceneMap({
purchase: PurchaseInvoiceView,
return: ReturnInvoiceView,
})}
onIndexChange={index => this.setState({ index })}
initialLayout={{ width: Dimensions.get("window").width }}
/>
</View>
)
}
}
const mapStateToProps = state => {
return {
userData: state.userData,
invoiceData: state.invoiceData,
returnInvoiceData: state.invoiceData.return,
}
}
export default connect(mapStateToProps)(InvoiceScreen)
when I am using this on Android it is working perfectly fine, but for an iOS it's crashing

Handling Refresh Token in React Native

I have an app authenticating fine and returning the access_token and refresh_token. I store them with AsyncStorage and save/get the access_token with redux. This is the very first app I am building and I am struggling with how and where to use the refresh_token.
This is the axios call in the component loginForm.js
axios({
url: `${base}/oauth/token`,
method: 'POST',
data: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
}
})
.then(response => {
setStatus({ succeeded: true });
// console.log(response.data);
deviceStorage.saveKey("userToken", response.data.access_token);
deviceStorage.saveKey("refreshToken", response.data.refresh_token);
Actions.main();
})
.catch(error => {
if (error.response) {
console.log(error);
}
});
This is the service deviceStorage.js
import { AsyncStorage } from 'react-native';
const deviceStorage = {
async saveItem(key, value) {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
}
};
export default deviceStorage;
This is the token action file
import { AsyncStorage } from 'react-native';
import {
GET_TOKEN,
SAVE_TOKEN,
REMOVE_TOKEN,
LOADING_TOKEN,
ERROR_TOKEN
} from '../types';
export const getToken = token => ({
type: GET_TOKEN,
token,
});
export const saveToken = token => ({
type: SAVE_TOKEN,
token
});
export const removeToken = () => ({
type: REMOVE_TOKEN,
});
export const loading = bool => ({
type: LOADING_TOKEN,
isLoading: bool,
});
export const error = tokenError => ({
type: ERROR_TOKEN,
tokenError,
});
export const getUserToken = () => dispatch =>
AsyncStorage.getItem('userToken')
.then((data) => {
dispatch(loading(false));
dispatch(getToken(data));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
export const saveUserToken = (data) => dispatch =>
AsyncStorage.setItem('userToken', data)
.then(() => {
dispatch(loading(false));
dispatch(saveToken('token saved'));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
export const removeUserToken = () => dispatch =>
AsyncStorage.removeItem('userToken')
.then((data) => {
dispatch(loading(false));
dispatch(removeToken(data));
})
.catch((err) => {
dispatch(loading(false));
dispatch(error(err.message || 'ERROR'));
});
This is the token reducer file
import {
GET_TOKEN,
SAVE_TOKEN,
REMOVE_TOKEN,
LOADING_TOKEN,
ERROR_TOKEN
} from '../actions/types';
const INITIAL_STATE = {
token: {},
loading: true,
error: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TOKEN:
return {
...state,
token: action.token
};
case SAVE_TOKEN:
return {
...state,
token: action.token
};
case REMOVE_TOKEN:
return {
...state,
token: action.token
};
case LOADING_TOKEN:
return {
...state,
loading: action.isLoading
};
case ERROR_TOKEN:
return {
...state,
error: action.error
};
default:
return state;
}
};
And this is the authentication file
import React from 'react';
import {
StatusBar,
StyleSheet,
View,
} from 'react-native';
import { connect } from 'react-redux';
import { Actions } from 'react-native-router-flux';
import { Spinner } from '../common';
import { getUserToken } from '../../actions';
class AuthLoadingScreen extends React.Component {
componentDidMount() {
this.bootstrapAsync();
}
bootstrapAsync = () => {
this.props.getUserToken().then(() => {
if (this.props.token.token !== null) {
Actions.main();
} else {
Actions.auth();
}
})
.catch(error => {
this.setState({ error });
});
};
render() {
return (
<View style={styles.container}>
<Spinner />
<StatusBar barStyle="default" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
});
const mapStateToProps = state => ({
token: state.token,
});
const mapDispatchToProps = dispatch => ({
getUserToken: () => dispatch(getUserToken()),
});
export default connect(mapStateToProps, mapDispatchToProps)(AuthLoadingScreen);
I believe I need to create an action and reducer to get the refresh_token (is that correct?) but I do not know what to do with it and where to call it (perhaps in the authentication file?).
Any help with this possibly with code examples related to my code would be massively appreciated. Thanks
Below are the steps
Do Login , get accessToken , refreshToken from response and save it to AsyncStorage.
Make common function for API calling
async function makeRequest(method, url, params, type) {
const token = await AsyncStorage.getItem('access_token');
let options = {
method: method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
if (!token) {
delete options['Authorization'];
}
if (['GET', 'OPTIONS'].includes(method)) {
url += (url.indexOf('?') === -1 ? '?' : '&') + queryParams(params);
} else {
Object.assign(options, {body: JSON.stringify(params)});
}
const response = fetch(ENV.API_URL+url, options);
return response;
}
Make one method in redux for getAceessTokenFromRefreshToken.
Use this method when session is expired
How do you know session is expired?
From each API calling if you get response like (440 response code) in
async componentWillReceiveProps(nextProps) {
if (nextProps.followResponse && nextProps.followResponse != this.props.followResponse) {
if (nextProps.followResponse.status) {
if (nextProps.followResponse.status == 440) {
// call here get acceesstokenfrom refresh token method and save again accesstoken in asyncstorage and continue calling to API
}
}
}
}

"Redux is not working properly with login"

I have implemented redux with my login form which is not working properly
it shows ERROR 'Cannot read property 'isAuthenticated' of undefined. which i think it means that redux is not implemented properly.
Please Help!
if you need any other file,tell me i will share it to you.
LoginForm.js
import React, {Component} from 'react';
import {
//StyleSheet,
View,
Text,
TextInput,
Button
} from 'react-native';
import {reduxForm,Field} from 'redux-form';
import {connect} from 'react-redux';
import {login} from './authActions';
const validate = values =>{
const errors = {};
if(!values.email){
errors.email="Please fill the email"
return errors;
}
if(!values.password){
errors.password="Please fill the password"
return errors;
}
}
const myFields = ({label,meta:{error,touched}, input:{onChange}}) =>{
return(
<View>
<Text>{label}</Text>
<TextInput style={{borderWidth:1,width:300,marginBottom:10}}
onChangeText={onChange}/>
{touched && (error && (<Text style={{color:'red'}}>{error}</Text>))}
</View>
);
}
const passFields = ({label,meta:{error,touched}, input:{onChange}}) =>{
return(
<View>
<Text>{label}</Text>
<TextInput style={{borderWidth:1,width:300,marginBottom:10}}
secureTextEntry={true}
onChangeText={onChange}/>
{touched && (error && (<Text style={{color:'red'}}>{error}</Text>))}
</View>
);
}
const submitbtn = values =>{
//alert(`here are the values ${JSON.stringify(values)}`);
//console.log(input.value);
this.props.login(values);
}
const myLoginForm = props => {
const {handleSubmit} = props;
return(
<View>
<Field
name="email"
component={myFields}
label="Email"/>
<Field
name="password"
component={passFields}
label="Password"
/>
<Button title="Submit"
onPress={handleSubmit(submitbtn)}/>
</View>
);
}
const LoginForm = reduxForm({
form:'loginform',
validate
})(myLoginForm);
const mapStateToProps =(state) =>({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps,{login})(LoginForm);
authActions.js
import axios from 'axios';
//import { returnErrors } from './errorActions';
export const register = ({username, name, email, password}) => {
return (dispatch, getState) => {
const config = {
headers : {
'Content-type' : 'Application/json'
}
}
const body = JSON.stringify({
username,
name,
email,
password
})
axios.post('http://localhost:5000/users/register', body , config )
.then(res => dispatch({
type : 'REGISTER_SUCCESS',
payload : res.data
}))
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status, 'REGISTER_FAIL'));
dispatch({
type : 'REGISTER_FAIL'
})
});
};
};
export const login = ({username, password}) => {
return (dispatch, getState) => {
const config = {
headers : {
'Content-type' : 'Application/json'
}
}
const body = JSON.stringify({
username,
password
})
axios.post('http://localhost:5000/users/login', body , config )
.then(res => dispatch({
type : 'LOGIN_SUCCESS',
payload : res.data
}))
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status, 'LOGIN_FAIL'));
dispatch({
type : 'LOGIN_FAIL'
})
});
};
}
export const logout = () => {
return {
type : 'LOGOUT_SUCCESS'
}
}
export const loadUser = () => {
return (dispatch, getState) => {
dispatch({
type: 'USER_LOADING',
});
axios.get('http://localhost:5000/users/auth' , tokenConfig(getState))
.then(res => dispatch({
type: 'USER_LOADED',
payload : res.data
}))
.catch(err => {
dispatch(returnErrors(err.response.data.message, err.response.status));
dispatch({
type : 'AUTH_ERROR'
});
})
}
}
//
export const tokenConfig = (getState) => {
const token = getState().auth.token;
const config = {
headers : {
'content-type' : 'Application/json',
}
}
if(token) {
config.headers['auth'] = token;
}
return config;
}
authReducer.js
const initState = {
toke: localStorage.getItem('token'),
isAuthenticated: null,
isLoading: null,
user: null
};
const authReducer = (state = initState, action) => {
switch(action.type){
case 'USER_LOADING':
return{
...state,
isLoading: true
}
case 'USER_LOADED':
return{
...state,
isLoading: false,
isAuthenticated:true,
user:action.payload
}
case 'REGISTER_SUCCESS':
case 'LOGIN_SUCCESS':
localStorage.setItem('token', action.payload.token)
return{
...state,
...action.payload,
isLoading: false,
isAuthenticated:true,
}
case 'AUTH_ERROR':
case 'LOGOUT_SUCCESS':
case 'LOGIN_FAIL':
case 'REGISTER_FAIL':
localStorage.removeItem('token')
return{
token: null,
user: null,
isLoading: false,
isAuthenticated:false,
}
default:
return state;
}
}
export default authReducer
Please check file where you combine your reducers, there's possibility that you put your reducer as something other than "auth" or even you forgotten to put the reducer here.
You can install "redux-devtools-extension" package and it's chrome extension to see which reducers are connected to your redux state and debug it more easily. Also, redux-form requires you to pass the form reducer you create along with all the other reducers you have in 'combineReducers'. Follow the instructions on their docs https://redux-form.com/8.2.2/docs/gettingstarted.md/
hope this helps :)

fetching values from server for multiselect picker react native

I tried fetching values from server for multi select picker component from the package https://github.com/toystars/react-native-multiple-select. But i get an error message: TypeError: null is not an object(evaluating this.state.LangKnown).
Please Kindly help.Thank u
My JSON values
{
"MFBasic": {
"SkinTones": "DARK,FAIR,VFAIR",
"Build": "SLIM,ATHLETIC,PLUMPY",
"Gender": "F,M,T",
"Genre": "ACTION,COMEDY,DRAMA",
"Languages": "ENG,HINDI,TAM",
"MediaModes": "ADS,MOVIES,SHORTFILMS",
"Tags": "BIKES,HOME,JEWELLARY"
},
"Result": "Successfully Loaded MF Basic Details",
"Code": 100
}
import React, {Component} from "react";
import { Text, View, StyleSheet, Picker, Alert } from "react-native";
import MultiSelect from "react-native-multiple-select";
export default class App extends React.Component {
state = {
LangPickerValueHolder: [],
LangKnown: []
}
componentDidMount () {
fetch('https://movieworld.sramaswamy.com/GetMFBasicDetails.php', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then(response => response.json())
.then(responseJson => {
let langString = responseJson.MFBasic.Languages;
let langArray = langString.split(',');
this.setState({
LangPickerValueHolder: langArray
});
}).catch(error => {
console.error(error);
});
}
render () {
return (
<View style={styles.itemContainer}>
{<MultiSelect
ref={(component) => { this.multiSelect = component; }}
onSelectedItemsChange={(value) =>
this.setState({ LangKnown: value })
}
selectedItems={this.state.LangKnown}
onChangeInput={ (text) => console.log(text)}
displayKey = ''name
submitButtonText="Submit">
{this.state.LangPickerValueHolder.map((item, key) => (
<MultiSelect.Item item = {item} uniqueKey = {key}/>
))}
</MultiSelect>}
</View>
);
}
}
You've made a good attempt at how to set up the MultiSelect however there are a couple of issues that need to be resolved.
If you look at the dependency the data that should be passed to it should be an array of objects. The example gives the object as { id: '92iijs7yta', name: 'Ondo' } We can easily transform your data from an array of strings into an array of objects that match the example.
let LangPickerValueHolder = langArray.map((name, id) => { return { name, id }; });
Using a map we can convert the array.
This would make your componentDidMount look like the following:
componentDidMount () {
fetch('https://movieworld.sramaswamy.com/GetMFBasicDetails.php', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then(response => response.json())
.then(responseJson => {
let langString = responseJson.MFBasic.Languages;
let langArray = langString.split(',');
let LangPickerValueHolder = langArray.map((name, id) => { return { name, id }; }); // <- here we had the mapping function
this.setState({ LangPickerValueHolder }); // <- save the new array of objects into the state
console.log(langArray);
}).catch(error => {
console.error(error);
});
}
Setting up the MultiSelect component requires a few more changes.
Firstly there is no MultiSelect.Item so the map that you are using to populate the MultiSelect won't work. Instead you need to use the items prop to set the items. Next you need to tell the MultiSelect component the correct uniqueKey prop (which in our case will be id) and set the displayKey correctly.
Here is what your render could look like.
render () {
return (
<View style={styles.container}>
<MultiSelect
ref={(component) => { this.multiSelect = component; }}
onSelectedItemsChange={(value) =>
this.setState({ LangKnown: value })
}
uniqueKey="id"
items={this.state.LangPickerValueHolder}
selectedItems={this.state.LangKnown}
onChangeInput={ (text) => console.log(text)}
displayKey = 'name'
submitButtonText="Submit" />
</View>
);
}
Here is it put together in a snack: https://snack.expo.io/#andypandy/multiselect-with-data-from-api
Here is the code from the snack:
import React from 'react';
import { View, StyleSheet } from 'react-native';
import MultiSelect from 'react-native-multiple-select';
export default class App extends React.Component {
// declaring state like this is absolutely fine, it doesn't need to be in a constructor
state = {
LangPickerValueHolder: [],
LangKnown: []
}
componentDidMount () {
fetch('https://movieworld.sramaswamy.com/GetMFBasicDetails.php', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then(response => response.json())
.then(responseJson => {
let langString = responseJson.MFBasic.Languages;
let langArray = langString.split(',');
let LangPickerValueHolder = langArray.map((name, id) => { return { name, id }; });
this.setState({
LangPickerValueHolder
});
console.log(langArray);
}).catch(error => {
console.error(error);
});
}
render () {
return (
<View style={styles.container}>
<MultiSelect
ref={(component) => { this.multiSelect = component; }}
onSelectedItemsChange={(value) =>
this.setState({ LangKnown: value })
}
uniqueKey="id" // <- set the value for the uniqueKey
items={this.state.LangPickerValueHolder} // <- set the items you wish to show
selectedItems={this.state.LangKnown}
onChangeInput={ (text) => console.log(text)}
displayKey = 'name' . // <- fix typo here
submitButtonText="Submit" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: 'white',
padding: 8
}
});

Storing a value in Redux

I am building a React Native app, mainly for verifying tickets, to be used by event administrators. The back-end is served by a Laravel app with a working OAuth2-server. I have a working login against that server but now I need to store the access token, to request data, such as events, and to verify if a ticket is matched for a given event.
I'm trying to implement Redux to store the access token etc. The login form I have updates the store via actions correctly, but I can't get it to work with the access token.
Here is the login screen:
import React, { Component } from 'react';
import { Text, View, TextInput, Button } from 'react-native';
import { connect } from 'react-redux'
import StringifyBody from './../lib/oauth2/StringifyBody'
import { login, storeTokens } from '../redux/actions/auth.js'
class Login extends Component {
constructor (props) {
super(props);
this.state = {
route: 'Login',
loading: false,
email: '',
password: '',
accessToken: '',
};
}
handleClick (e) {
e.preventDefault();
return new Promise(function(resolve, reject) {
var data = StringifyBody(this.state.password, this.state.email)
// XHR settings
var xhr = new XMLHttpRequest()
xhr.withCredentials = true
xhr.onerror = function() {
reject(Error('There was a network error.'))
}
xhr.open("POST", "http://192.168.0.141/oauth/access_token")
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded")
xhr.send(data)
xhr.onloadend = function() {
if (xhr.status === 200) {
var parsedJson = JSON.parse(xhr.response)
responseArray = []
for(var i in parsedJson) {
responseArray.push([parsedJson [i]])
}
// assign values to appropriate variables
let accessToken = responseArray[0];
console.log('access token is: ' + accessToken)
accessToken => this.setState({ access_token: accessToken })
this.props.tokenStore(this.state.accessToken) // This doesn't work: "cannot read property 'tokenStore' of undefined"
resolve(xhr.response)
} else {
reject(Error('Whoops! something went wrong. Error: ' + xhr.statusText))
}
}
})
.done(() => {
this.props.onLogin(this.state.email, this.state.password); // This works
})
}
render() {
return (
<View style={{padding: 20}}>
<Text style={{fontSize: 27}}>{this.state.route}</Text>
<TextInput
placeholder='Email'
autoCapitalize='none'
autoCorrect={false}
keyboardType='email-address'
value={this.state.email}
onChangeText={(value) => this.setState({ email: value })} />
<TextInput
placeholder='Password'
autoCapitalize='none'
autoCorrect={false}
secureTextEntry={true}
value={this.state.password}
onChangeText={(value) => this.setState({ password: value })} />
<View style={{margin: 7}}/>
<Button onPress={(e) => this.handleClick(e)} title={this.state.route}/>
</View>
);
}
}
const mapStateToProps = state => {
return {
isLoggedIn: state.auth.isLoggedIn,
access_token: state.auth.access_token,
}
}
const mapDispatchToProps = (dispatch) => {
return {
onLogin: (email, password) => { dispatch(login(email, password)); },
tokenStore: (accessToken) => { dispatch(storeTokens(accessToken)) },
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Redux actions:
export const login = (email, password) => {
return {
type: 'LOGIN',
email: email,
password: password
};
};
export const logout = () => {
return {
type: 'LOGOUT'
};
};
export const storeTokens = () => {
return {
type: 'STORE_TOKENS',
access_token: accessToken,
}
}
And finally the reducers:
const defaultState = {
isLoggedIn: false,
email: '',
password: '',
access_token: '',
};
export default function reducer(state = defaultState, action) {
switch (action.type) {
case 'LOGIN':
return Object.assign({}, state, {
isLoggedIn: true,
email: action.email,
password: action.password
});
case 'LOGOUT':
return Object.assign({}, state, {
isLoggedIn: false,
email: '',
password: ''
});
case 'STORE_TOKENS':
return Object.assign({}, state, {
access_token: action.accessToken,
})
default:
return state;
}
}
I've also tried passing the data to this.props.storeTokens (the actual action) in a componentDidMount() which gives me the error undefined is not a function (evaluating 'this.props.storeTokens()') componentDidMount Login.js:57:8
My question then is: How do I store the variable I get from my XHR POST in the redux store? Why is this.props.tokenStore and this.props.storeToken not defined?
Hey thats a mistake owing to javascript concept. You are calling
this.props.tokenStore(this..state.accessToken) // This doesn't work: "cannot read property 'tokenStore' of undefined"
inside a function defined using ES5 syntax. either you store the reference of this outside the function in some variable and then use that variable instead of this. The other option is define arrow function instead. So change your function keyword into
() =>
and this should work. this as of now in your implementation doesn't point to component that you are thinking