I created a simple form to using firebase authentication with react native. But when I try, my state always show success even failed to auth.
Success label always appear before then followed by a failed label. Although in fact the same enter the wrong password with the correct email.
Here is my code :
//import libraries
import React, { Component } from 'react';
import { View, Text, ToastAndroid } from 'react-native';
import { Button, Card, CardSection, Input, Spinner } from './common';
import firebase from 'firebase';
// create a component
class LoginForm extends Component {
state = { email: '', password:'', error: '', success: '', loading: false };
onButtonPress() {
const {email, password} = this.state;
if(this.state.email == '') {
this.setState({ error: 'Email can not be empty', loading: false });
} else if(this.state.password == '') {
this.setState({ error: 'Password can not be emptyg', loading: false });
} else if(!this.validateEmail(this.state.email)) {
this.setState({ error: 'Email not valid', loading: false });
} else {
this.setState({ error: '', success: '', loading: true });
firebase.auth().signInWithEmailAndPassword(email, password)
.then(this.onLoginSuccess())
.catch(() => {
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(this.onLoginSuccess())
.catch(() => {
this.onLoginFail()
});
});
}
}
validateEmail = (email) => {
var 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,}))$/;
return re.test(email);
};
onLoginFail() {
this.setState({
error: 'Authentication failed.',
loading: false,
});
}
onLoginSuccess() {
this.setState({
loading: false,
error: '',
email: '',
password: '',
success: 'Authentication success.',
});
}
renderButton() {
if(this.state.loading) {
return <Spinner />;
}
return (
<Button onPress={this.onButtonPress.bind(this)}>Log In</Button>
);
}
renderError() {
if(this.state.error) {
return <Text style={styles.errorTextStyle}>
{this.state.error}
</Text>;
}
}
renderSuccess() {
if(this.state.success) {
return <Text style={styles.successTextStyle}>
{this.state.success}
</Text>;
}
}
render() {
return (
<View>
<Card>
<CardSection>
<Input
placeholder="email#gmail.com"
label="Email"
value={this.state.email}
onChangeText={ email => this.setState({ email })}
/>
</CardSection>
<CardSection>
<Input
secureTextEntry
placeholder="password"
label="Password"
value={this.state.password}
onChangeText={ password => this.setState({ password })}
/>
</CardSection>
{ this.renderError() }
{ this.renderSuccess() }
<CardSection>
{ this.renderButton() }
</CardSection>
</Card>
</View>
);
}
}
const styles = {
errorTextStyle: {
fontSize: 15,
alignSelf: 'center',
color: 'red'
},
successTextStyle: {
fontSize: 15,
alignSelf: 'center',
color: 'green'
}
};
//make this component available to the app
export default LoginForm;
Check the state.error and state.success are neither empty strings.
renderError() {
if(this.state.error && this.state.error !== '') {
return <Text style={styles.errorTextStyle}>
{this.state.error}
</Text>;
}
}
renderSuccess() {
if(this.state.success && this.state.success !== '') {
return <Text style={styles.successTextStyle}>
{this.state.success}
</Text>;
}
}
The other option is to set to null said values when setting them out, so you won't need the not empty string condition.
Also, use identity operators (===) instead of quality operators (==).
onButtonPress() {
const {email, password} = this.state;
if(this.state.email === '') {
this.setState({ error: 'Email can not be empty', loading: false });
} else if(this.state.password === '') {
this.setState({ error: 'Password can not be emptyg', loading: false });
} else if(!this.validateEmail(this.state.email)) {
this.setState({ error: 'Email not valid', loading: false });
} else {
this.setState({ error: '', success: '', loading: true });
firebase.auth().signInWithEmailAndPassword(email, password)
.then(this.onLoginSuccess())
.catch(() => {
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(this.onLoginSuccess())
.catch(() => {
this.onLoginFail()
});
});
}
}
Related
I have a react app,
I am trying to perform login using redux and PHP.
I have a component, the component contains a form. the user enters the password and email to the form. after submitting the form the data enter an async-await function called handleSubmit.
That function has another function called onSubmitLogin in the await.
from the onSubmit this goes to the actiOn creator in ajax.js file.
the next step is the API PHP code, in there the PHP function checks if the user exists.
now from there to the reducer and back to function via mapStateToProps,
I want the states notActiveUserError and UserDoesNotExist to change depending on the props (this.props.auth) value I receive from the reducer using the checkUserValidation function.
The problem I have is that the props change but the state is not changing on the first run, every other time it works amazing but it never works on the first time after page loads.
Any help would be great.
this is the code I have:
handleSubmit is in LoginForm.js (full component is at the bottom of the question)
handleSubmit = async (event) => {
await this.onSubmitLogin(event);
this.checkUserValidation();
}
onSubmitLogin is in LoginForm.js (full component is at the bottom of the question)
onSubmitLogin(event){
event.preventDefault();
if(this.clientValidate()){
this.clientValidate();
}else{
let userData ={
email: this.state.email,
password: this.state.password
}
this.props.userLogin(userData);
}
}
the action creator
export const userLogin = (userData) => {
return (dispatch) => {
axios({
url: `${API_PATH}/users/Login.php`,
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: JSON.stringify(userData)
})
.then(function(response) {
dispatch({ type: USER_LOGIN, value: response.data });
})
.catch(function(error) {
console.log(error);
});
}
}
LoginForm component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Redirect, Link } from 'react-router-dom';
import {
Button,
Form,
FormGroup,
FormControl,
Col,
Alert,
Grid,
Row
} from 'react-bootstrap';
import { userLogedIn } from '../../actions';
import { userLogin } from '../../actions/ajax';
class LoginForm extends Component {
constructor() {
super();
this.state={
email: '',
username: '',
password: '',
auth: false,
usernameError: '',
passwordError: '',
EmptyUsernameError: '',
EmptyPasswordError: '',
notActiveUserError: '',
UserDoesNotExist: '',
userid: ''
}
this.handleSubmit = this.handleSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
clientValidate = () => {
let isError = false;
if(this.state.email === ''){
this.setState({EmptyUsernameError: 'לא הזנתם דואר אלקטרוני'});
}
if(this.state.password === ''){
isError = true;
this.setState({EmptyPasswordError: 'לא הזנתם סיסמה'});
}
return isError;
}
checkUserValidation(){
if(this.props.auth === false && this.props.userid !== undefined){
console.log('this.props 1', this.props);
this.setState({notActiveUserError: 'חשבון לא מאומת'});
}
if(this.props.auth === false && this.props.userid === undefined){
console.log('this.props 2', this.props);
this.setState({UserDoesNotExist: 'משתשמ לא קיים'});
}
}
onSubmitLogin(event){
event.preventDefault();
if(this.clientValidate()){
this.clientValidate();
}else{
let userData ={
email: this.state.email,
password: this.state.password
}
this.props.userLogin(userData);
}
}
handleSubmit = async (event) => {
await this.onSubmitLogin(event);
this.checkUserValidation();
}
redirectUser = () => {
if(this.props.auth === true && this.props.userid != null){
const timestamp = new Date().getTime(); // current time
const exp = timestamp + (60 * 60 * 24 * 1000 * 7) // add one week
let auth = `auth=${this.props.auth};expires=${exp}`;
let userid = `userid=${this.props.userid};expires=${exp}`;
document.cookie = auth;
document.cookie = userid;
return <Redirect to='/records/biblist' />
}
}
onChange(event){
this.setState({
[event.target.name]: event.target.value,
auth: false,
usernameError: '',
EmptyPasswordError: '',
EmptyUsernameError: '',
notActiveUserError: '',
UserDoesNotExist: ''
})
}
isLoggedIn = () =>{
console.log(' this.props.auth ', this.props.auth);
}
render() {
this.isLoggedIn();
return (
<Form>
<FormGroup controlId="formHorizontalusername">
<Col xs={12} sm={5} style={TopMarginLoginBtn}>
<Row style={marginBottomZero}>
<FormControl ref="email" name="email" type="email" onChange={this.onChange} placeholder="דואר אלקטרוני" aria-label="דואר אלקטרוני"/>
</Row>
</Col>
<Col xs={12} sm={4} style={TopMarginLoginBtn}>
<Row style={marginBottomZero}>
<FormControl ref="password" name="password" type="password" onChange={this.onChange} placeholder="הקלד סיסמה" aria-label="סיסמה"/>
</Row>
</Col>
<Col xs={12} sm={3} style={TopMarginLoginBtn} >
<Button onClick={this.handleSubmit} type="submit" className="full-width-btn" id="loginSubmit">התחבר</Button>
{this.redirectUser()}
</Col>
<Col xs={12}>
<Link to="/passwordrecovery">שכחתי את הסיסמה</Link>
</Col>
</FormGroup>
{
this.state.EmptyUsernameError ?
<Alert bsStyle="danger"> {this.state.EmptyUsernameError} </Alert> :
''
}
{
this.state.EmptyPasswordError ?
<Alert bsStyle="danger"> {this.state.EmptyPasswordError} </Alert> :
''
}
{
this.state.usernameError ?
<Alert bsStyle="danger"> {this.state.usernameError} </Alert> :
''
}
{
//PROBLEM!! state updates before props
this.state.notActiveUserError ?
<Alert bsStyle="danger">{this.state.notActiveUserError}</Alert> :
''
}
{
//PROBLEM!! state updates before props
this.state.UserDoesNotExist ?
<Alert bsStyle="danger">{this.state.UserDoesNotExist} </Alert> :
''
}
<Row className="show-grid">
</Row>
</Form>
);
}
}
const bold={
fontWeight: 'bolder'
}
const mapDispatchToProps = dispatch => {
return {
userLogedIn: (params) => dispatch(userLogedIn(params))
};
};
const mapStateToProps = state => {
return {
userid: state.authReducer.userid,
auth: state.authReducer.auth,
email: state.authReducer.email
}
}
export default connect(mapStateToProps, {userLogedIn, userLogin})(LoginForm);
If you want to use async-await in your component then you have to move your API call to your component because when you call the action from component it doesn't return data back to your component.
if you want to use redux then I suggest you should remove async-await from your component it won't work, instead use the redux state to store success or failed state and handle that change in your component from getDerivedStateFromProps
export const userLogin = (userData) => {
return (dispatch) => {
dispatch({ type: USER_LOGIN_BEGIN }); // reset error/login state
axios({
url: `${API_PATH}/users/Login.php`,
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: JSON.stringify(userData)
})
.then(function(response) {
dispatch({ type: USER_LOGIN, value: response.data });
})
.catch(function(error) {
dispatch({ type: USER_LOGIN_FAILED, value: error });
});
}
}
in your component
onSubmitLogin(event){
event.preventDefault();
if(!this.clientValidate()){
let userData ={
email: this.state.email,
password: this.state.password
}
this.props.userLogin(userData);
}
}
handleSubmit = (event) => {
this.onSubmitLogin(event);
// this.checkUserValidation // move this logic to reducer and set error there according to response
}
static getDerivedStateFromProps(nextProps, prevState) {
// handle success/error according to your need and return update state
}
I have a simple form with 3 inputs and one button.
The submit button is disabled by default, and each input has custom validation logic with regex.
How can I enable the button again when all validation passes?
This is my component:
import React, { Component } from 'react';
import { Input, Upload , Icon, message} from 'antd';
import Form from '../../components/uielements/form';
import Checkbox from '../../components/uielements/checkbox';
import Button from '../../components/uielements/button';
import Notification from '../../components/notification';
import { adalApiFetch } from '../../adalConfig';
const FormItem = Form.Item;
class RegisterTenantForm extends Component {
constructor(props) {
super(props);
this.state = {TenantId: '', TenantUrl: '', CertificatePassword: '', confirmDirty: false, loading: false, buttondisabled: true };
this.handleChangeTenantUrl = this.handleChangeTenantUrl.bind(this);
this.handleChangeCertificatePassword = this.handleChangeCertificatePassword.bind(this);
this.handleChangeTenantId= this.handleChangeTenantId.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleupload = this.handleupload.bind(this);
this.handleTenantIdValidation = this.handleTenantIdValidation.bind(this);
this.handleTenantAdminUrl = this.handleTenantAdminUrl.bind(this);
};
handleChangeTenantUrl(event){
this.setState({TenantUrl: event.target.value});
}
handleChangeCertificatePassword(event){
this.setState({CertificatePassword: event.target.value});
}
handleChangeTenantId(event){
this.setState({TenantId: event.target.value});
}
beforeUpload(file) {
const isJPG = file.type === 'image/jpeg';
if (!isJPG) {
message.error('You can only upload JPG file!');
}
}
handleupload(info){
//let files = e.target.files;
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
this.setState({ loading: false });
this.setState({ 'selectedFile': info.file });
}
}
handleTenantIdValidation(rule, value, callback){
const form = this.props.form;
var re = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (!form.getFieldValue('tenantid').match(re)) {
callback('Tenant id is not correctly formated id');
}
else {
callback();
}
}
handleTenantAdminUrl(rule, value, callback){
const form = this.props.form;
var re = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]#!\$&'\(\)\*\+,;=.]+$/i;
if (!form.getFieldValue('tenantadminurl').match(re)) {
callback('Tenant Url is not correctly formated id');
}
else {
callback();
}
}
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
/*Notification(
'success',
'Received values of form',
JSON.stringify(values)
);*/
let data = new FormData();
//Append files to form data
data.append("model", JSON.stringify({ "TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "CertificatePassword": this.state.CertificatePassword }));
//data.append("model", {"TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "TenantPassword": this.state.TenantPassword });
let files = this.state.selectedFile;
for (let i = 0; i < files.length; i++) {
data.append("file", files[i], files[i].name);
}
const options = {
method: 'put',
body: data,
config: {
headers: {
'Content-Type': 'multipart/form-data'
}
}
};
adalApiFetch(fetch, "/Tenant", options)
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
this.setState({ data: responseJson });
}
})
.catch(error => {
console.error(error);
});
}
});
}
render() {
const uploadButton = (
<div>
<Icon type={this.state.loading ? 'loading' : 'plus'} />
<div className="ant-upload-text">Upload</div>
</div>
);
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 14 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 14,
offset: 6,
},
},
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="Tenant Id" hasFeedback>
{getFieldDecorator('tenantid', {
rules: [
{
required: true,
message: 'Please input your tenant id',
},
{
validator: this.handleTenantIdValidation
}],
})(<Input name="tenantid" id="tenantid" onChange={this.handleChangeTenantId}/>)}
</FormItem>
<FormItem {...formItemLayout} label="Certificate Password" hasFeedback>
{getFieldDecorator('certificatepassword', {
rules: [
{
required: true,
message: 'Please input your password!',
}
],
})(<Input type="password" name="certificatepassword" id="certificatepassword" onChange={this.handleChangeCertificatePassword}/>)}
</FormItem>
<FormItem {...formItemLayout} label="Tenant admin url" hasFeedback>
{getFieldDecorator('tenantadminurl', {
rules: [
{
required: true,
message: 'Please input your tenant admin url!',
},
{
validator: this.handleTenantAdminUrl
}],
})(<Input name="tenantadminurl" id="tenantadminurl" onChange={this.handleChangeTenantUrl} />)}
</FormItem>
<FormItem {...formItemLayout} label="Certificate File">
<Upload onChange={this.handleupload} beforeUpload={this.beforeUpload}>
<Button >
<Icon type="upload" /> Click to Upload
</Button>
</Upload>
</FormItem>
<FormItem {...tailFormItemLayout}>
<Button type="primary" htmlType="submit" disabled={this.state.buttondisabled}>
Register tenant
</Button>
</FormItem>
</Form>
);
}
}
const WrappedRegisterTenantForm = Form.create()(RegisterTenantForm);
export default WrappedRegisterTenantForm;
Update:
Answer should be provided using ant design API
In each validator if block, set buttonDisabledState state to true and in each else statement set it to false.
When all fields validations are passed then your btn disabled state will set to false.
Your button's disabled value is a boolean that comes from your component state.
So what you need to do is set that boolean to false when all checks have passed:
this.setState({buttonDisabled: false})
Add a boolean value to your state, i.e, isValidated that defaults to false and you change to true when all the validations pass. You're sorta already doing that with the state.buttondisabled - just setState that to false when the validations are all passing.
Have a look at this section, hopefully it will solve your problem
https://ant.design/components/form/#Form.Item
I am trying to use antd to create a form with a fileupload, but I cant make work the handleupload function, I have the error:
Cannot read property 'setState' of undefined
at handleupload (registertenantform.js:43)
The code is this:
import React, { Component } from 'react';
import { Input, Upload , Icon, message} from 'antd';
import Form from '../../components/uielements/form';
import Checkbox from '../../components/uielements/checkbox';
import Button from '../../components/uielements/button';
import Notification from '../../components/notification';
import { adalApiFetch } from '../../adalConfig';
const FormItem = Form.Item;
class RegisterTenantForm extends Component {
constructor(props) {
super(props);
this.state = {TenantId: '', TenantUrl: '', CertificatePassword: '' };
this.handleChangeTenantUrl = this.handleChangeTenantUrl.bind(this);
this.handleChangeCertificatePassword = this.handleChangeCertificatePassword.bind(this);
this.handleChangeTenantId= this.handleChangeTenantId.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChangeTenantUrl(event){
this.setState({TenantUrl: event.target.value});
}
handleChangeCertificatePassword(event){
this.setState({CertificatePassword: event.target.value});
}
handleChangeTenantId(event){
this.setState({TenantId: event.target.value});
}
beforeUpload(file) {
const isJPG = file.type === 'image/jpeg';
if (!isJPG) {
message.error('You can only upload JPG file!');
}
}
handleupload(info){
//let files = e.target.files;
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
this.setState({ loading: false });
this.setState({ 'selectedFiles': info.file });
}
}
state = {
confirmDirty: false,
loading: false,
};
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
/*Notification(
'success',
'Received values of form',
JSON.stringify(values)
);*/
let data = new FormData();
//Append files to form data
data.append("model", JSON.stringify({ "TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "CertificatePassword": this.state.CertificatePassword }));
//data.append("model", {"TenantId": this.state.TenantId, "TenantUrl": this.state.TenantUrl, "TenantPassword": this.state.TenantPassword });
let files = this.state.selectedFiles;
for (let i = 0; i < files.length; i++) {
data.append("file", files[i], files[i].name);
}
const options = {
method: 'put',
body: data,
config: {
headers: {
'Content-Type': 'multipart/form-data'
}
}
};
adalApiFetch(fetch, "/Tenant", options)
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
this.setState({ data: responseJson });
}
})
.catch(error => {
console.error(error);
});
}
});
}
render() {
const uploadButton = (
<div>
<Icon type={this.state.loading ? 'loading' : 'plus'} />
<div className="ant-upload-text">Upload</div>
</div>
);
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 14 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 14,
offset: 6,
},
},
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="Tenant Id" hasFeedback>
{getFieldDecorator('tenantid', {
rules: [
{
required: true,
message: 'Please input your tenant id',
},
],
})(<Input name="tenantid" id="tenantid" onChange={this.handleChangeTenantId}/>)}
</FormItem>
<FormItem {...formItemLayout} label="Certificate Password" hasFeedback>
{getFieldDecorator('certificatepassword', {
rules: [
{
required: true,
message: 'Please input your password!',
}
],
})(<Input type="certificatepassword" onChange={this.handleChangeCertificatePassword}/>)}
</FormItem>
<FormItem {...formItemLayout} label="Tenant admin url" hasFeedback>
{getFieldDecorator('tenantadminurl', {
rules: [
{
required: true,
message: 'Please input your tenant admin url!',
}
],
})(<Input type="tenantadminurl" onChange={this.handleChangeTenantUrl} />)}
</FormItem>
<FormItem {...tailFormItemLayout}>
<Upload onChange={this.handleupload} beforeUpload={this.beforeUpload}>
<Button>
<Icon type="upload" /> Click to Upload
</Button>
</Upload>
<Button type="primary" htmlType="submit">
Register tenant
</Button>
</FormItem>
</Form>
);
}
}
const WrappedRegisterTenantForm = Form.create()(RegisterTenantForm);
export default WrappedRegisterTenantForm;
You missed to bind your handleupload function. Just add
this.handleupload = this.handleupload.bind(this)
in your RegisterTenantForm constructor
Or
you can rewrite a handleupload function using arrow function like so:
handleupload = (info) => {
//let files = e.target.files;
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
// btw you dont need two separated setState here, you can do
// it in one setState
this.setState({
'selectedFiles': info.file,
loading: false
});
}
}
Your this is treating local scope inside promise as a result setState method should be undefined so you need to assign this into another variable like below:
const that = this;
and can access that into your promise code below:
adalApiFetch(fetch, "/Tenant", options)
.then(response => response.json())
.then(responseJson => {
if (!that.isCancelled) {
that.setState({
data: responseJson
});
}
})
.catch(error => {
console.error(error);
});
I am trying to build my first React+Redux app. Still in scaffolding stage + I am new in React.
I've created a simple login app where the AuthAction returns a Promise from login(username, password) function. Login works perfectly if the Promise return resolve but if Promise return reject I get this error.
Uncaught (in promise)
This is my Action: AuthAction.js
export function login(username, password){
return {
type: 'LOGGED_IN',
payload: new Promise((resolve, reject) => {
if (username === 'ariful.haque#icarasia.com'){
resolve(
{ isLoggedIn: true,
shouldRedirect: true,
errorMessage: null,
user: {username: username, password: password}
}
);
} else {
console.log('send reject');
reject(
{ isLoggedIn: false,
shouldRedirect: false,
errorMessage: 'login failed',
user: null
}
);
}
})
};
}
This is my Reducer: AuthReducer.js
const AuthReducer = (state =
{ isLoggedIn: false,
shouldRedirect: false,
user: null,
errorMessage: null
},
action) => {
switch (action.type) {
case 'LOGGED_IN_FULFILLED':
console.log('authReducer: ', 'LOGGED_IN_FULFILLED', action);
state = {
...state,
user: action.payload.user,
isLoggedIn: action.payload.isLoggedIn,
shouldRedirect: action.payload.shouldRedirect,
errorMessage: action.payload.errorMessage,
};
break;
case 'LOGIN_FAILED_FULFILLED':
console.log('authReducer: ', 'LOGIN_FAILED_FULFILLED');
state = {
...state,
user: action.payload,
isLoggedIn: action.payload.isLoggedIn,
shouldRedirect: action.payload.shouldRedirect,
};
break;
case 'LOGOUT':
state = {
...state,
user: action.payload.user,
isLoggedIn: action.payload.isLoggedIn,
shouldRedirect: action.payload.shouldRedirect,
};
break;
}
return state;
};
export default AuthReducer;
This is LoginPage Container
import React from 'react';
import {connect} from 'react-redux';
// Actions
import {login} from '../actions/AuthActions';
class LoginPage extends React.Component {
//React Component Hook
componentDidUpdate(){
if (this.props.auth.isLoggedIn) {
this.props.history.push('/dashboard');
} else{
console.log('auth props: ', this.props.auth);
}
}
/**
* Login function
* #param event
*/
testLogin = (event) => {
event.preventDefault();
console.log('login submit');
this.props.login(this.refs.username.value, this.refs.password.value);
};
render() {
//Set page title
document.title = 'Login - Seller Portal';
return (
<div style={{'padding': '10px','backgroundColor': '#ccc', 'border': '1px solid gray'}}>
<p>You must log in to view the page at</p>
<form id="login_form" onSubmit = { this.testLogin } >
<label>Username: <input type="text" name="username" ref="username" id="username" maxLength={20}/></label><br/>
<label>Password: <input type="password" name="password" ref="password" id="password" maxLength={20}/></label><br/>
<button type="submit">Log in</button> <br/>
</form>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
auth: state.authReducer,
};
};
const mapDispatchToProps = (dispatch) => {
return {
login: (username, password) => {
let l = login(username, password);
console.log('mapDispatchToProps ', l);
dispatch (l);
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage);
I am using Thunk and Redux-Promise-Middleware in my store.js
applyMiddleware(thunk, promise())
Question
Why am I getting this Uncaught (in promise) error in reject? though the resolve is working?
On reject promise, how can I show a error message in the LoginPage component?
Thanks in Advance.
How about redesign it a little bit?
Action:
function success(username, password) {
return {
type: 'LOGGED_IN_SUCCESS',
payload: {
isLoggedIn: true,
shouldRedirect: true,
errorMessage: null,
user: {username: username, password: password}
}
};
}
function fail() {
return {
type: 'LOGGED_IN_FAIL',
payload: {
isLoggedIn: false,
shouldRedirect: false,
errorMessage: 'login failed',
user: null
}
};
}
export function login(username, password){
return username === 'ariful.haque#icarasia.com' ? success(username, password) : fail();
}
And you can catch action.type as LOGGED_IN_SUCCESS and LOGGED_IN_FAIL in your AuthReducer.js
Okay i think this should help. A little bit of restructuring of action object is required.
const login = (username, password) => function (dispatch) {
new Promise((resolve, reject) => {
if (username === 'ariful.haque#icarasia.com') {
resolve(
{
isLoggedIn: true,
shouldRedirect: true,
errorMessage: null,
user: {username: username, password: password}
}
);
} else {
reject(
{
isLoggedIn: false,
shouldRedirect: false,
errorMessage: 'login failed',
user: null
}
)
}
})
.then(payload => {
dispatch({
type: 'LOGGED_IN',
payload,
})
})
.catch(payload => {
dispatch({
type: 'LOGGED_IN',
payload,
})
})
This will dispatch the correct object based on your promise resolve and reject
I'm developing a mobile application by use react-native and redux,thunk and it's the first time I write by react-native.
My problem is I call an api and the response is valid, I want to do somethings as update UI, navigate to new screen... for do that I will need to used flag in my component to mark it.
This is login example, after user login success, i want to navigate to Home screen. for do that, i need check an flag isLoginSuccess in props on the method componentWillReceiveProps to know user have been login success or not, but i think it's not good solution.
My question is we have other way to do it without use flag.
action.js
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGIN = "LOGIN";
export function login(username, password) {
console.log(username)
return {
type: LOGIN,
username: username,
password: password
}
}
export function loginSuccess(data) {
return {
type: LOGIN_SUCCESS,
loginData: data
}
}
export function loginFail(error) {
return {
type: LOGIN_FAIL,
error: error
}
}
export function doLogin(username, password) {
return (dispatch) => {
dispatch(login(username, password))
api.login(username, password)
.then(response => response.json())
.then(jsonData => {
console.log(jsonData)
dispatch(loginSuccess(jsonData))
})
.catch((error) => {
dispatch(loginFail(error))
})
}
}
reducer.js
const initialState = {
loginData:{},
isLoginDoing : false,
isLoginSuccess : false,
username :"",
password : "",
error : {},
}
export default function(state = initialState , action ={}){
switch(action.type){
case actionType.LOGIN:{
return {
...state,
username: action.username,
password: action.password,
isLoginDoing : true
}
}
case actionType.LOGIN_SUCCESS:{
return {
...state,
loginData: action.loginData,
isLoginDoing : false,
isLoginSuccess : true
}
}
case actionType.LOGIN_FAIL:{
return {
...state,
isLoginDoing : false,
isLoginSuccess : false,
error : action.error
}
}
default :{
return state
}
}
}
component.js
import { connect } from "react-redux"
import { bindActionCreators } from 'redux';
import { doLogin } from '../actions'
import BaseComponent from './baseComponent'
class Login extends BaseComponent {
constructor(props) {
super(props)
this.state = {
username: '',
password: '',
}
this.functionLogin = this.functionLogin.bind(this);
}
functionLogin() {
const { username, password } = this.state;
if(!this.props.loginReducer.isLoginDoing){
this.props.doLogin(username, password)
}
}
componentWillReceiveProps (nextProps) {
console.log("componentWillReceiveProps");
const { navigate, goBack } = this.props.navigation;
if(nextProps.loginReducer.isLoginSuccess){
// this._navigateTo('Home')
navigate('Home',nextProps.loginReducer.loginData);
}
}
render() {
const { navigate, goBack } = this.props.navigation;
return (
<View style={{ backgroundColor: 'color', marginTop: 10 }} >
<TextInput
style={{ height: 40 }}
placeholder="Username"
onChangeText={value => this.setState({ username: value })}
/>
<TextInput
style={{ height: 40 }}
placeholder="Password"
onChangeText={value => this.setState({ password: value })}
/>
<Button
onPress={this.functionLogin}
title="Login"
color="#841584"
/>
</View>
);
}
}
const mapStateToProps = (state) => {
console.log(state);
return {
loginReducer: state.loginReducer
};
}
function mapDispatchToProps(dispatch) {
return {
doLogin: (username, password) => dispatch(doLogin(username, password))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login)
Thanks
In your function doLogin, you can dispatch a navigation action after dispatch(loginSuccess(jsonData)).
For example for react-navigation (if you have integrated it with redux, if it's not the case, see https://reactnavigation.org/docs/guides/redux):
dispatch(NavigationActions.navigate({routeName: 'Home'});
(Don't forget import { NavigationActions } from 'react-navigation';)