UserRegister: Improve error handling

- Show field errors in line
- Simplify redux usage
This commit is contained in:
nikolark committed 2017-11-05 15:35:24 +01:00
1 parent 34bc0a3e30
commit 9285cdf953
8 files changed
+100 -53

No files matched your search

+1
View File
@@ -11,6 +11,7 @@ export function requestLogin(username, password) {
return (dispatch) => {
// We are now logging in
dispatch(loginRequest());
return fetch(`${Config.DUSKEN_API_URL}/auth/obtain-token/`, {
method: 'POST',
headers: {
+9 -12
View File
@@ -2,16 +2,15 @@ import React, {Component} from 'react';
import {connect} from "react-redux";
import Membership from "./Membership";
import ProofContainer from "./ProofContainer";
import { logout } from "../../actions";
import {requestUserData} from "../../api";
import ProofContainer from "./ProofContainer";
class MembershipContainer extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: props.isAuthenticated,
isFetchingUserData: props.isFetchingUserData,
userToken: props.userToken,
lastOrder: props.lastOrder,
@@ -19,9 +18,6 @@ class MembershipContainer extends Component {
}
componentWillReceiveProps(nextProps) {
if(this.state.isAuthenticated !== nextProps.isAuthenticated) {
this.setState({isAuthenticated: nextProps.isAuthenticated})
}
if(this.state.userToken !== nextProps.userToken) {
this.setState({userToken: nextProps.userToken}, () => {
if(nextProps.userToken) {
@@ -60,6 +56,7 @@ class MembershipContainer extends Component {
if (this.props.user) {
return <ProofContainer
user={this.props.user}
isAuthenticated={this.props.isAuthenticated}
userToken={this.state.userToken}
fetchUser={this.fetchUser}
onLogoutPress={this.onLogoutPress}
@@ -69,17 +66,17 @@ class MembershipContainer extends Component {
}
}
function stateToProps(state) {
function storeToProps(store) {
return {
isAuthenticated: state.isAuthenticated,
isFetchingUserData: state.isFetchingUserData,
userToken: state.userToken,
user: state.user,
lastOrder: state.lastOrder
isAuthenticated: store.isAuthenticated,
isFetchingUserData: store.isFetchingUserData,
userToken: store.userToken,
user: store.user,
lastOrder: store.lastOrder
};
}
export default connect(
stateToProps,
storeToProps,
{ requestUserData, logout }
)(MembershipContainer);
+52 -22
View File
@@ -1,6 +1,8 @@
import React, {Component} from 'react';
import {Container, Header, Content, Form, Item, Input, Label, Spinner, Button, Text, Icon, Toast} from 'native-base';
import {Platform, StyleSheet, View} from "react-native";
import EmailValidator from 'email-validator';
import platform from "native-base/src/theme/variables/platform";
export default class UserRegister extends Component {
constructor(props) {
@@ -27,7 +29,9 @@ export default class UserRegister extends Component {
for(let key of fieldNames) {
const value = this.state[key];
if(value === '') {
errors[key] = 'Feltet kan ikke være tomt';
errors[key] = 'kan ikke være tomt';
} else if(key === 'email' && !EmailValidator.validate(value)) {
errors[key] = 'er ikke en gyldig e-post';
} else {
delete errors[key];
}
@@ -50,12 +54,13 @@ export default class UserRegister extends Component {
<Input
autoFocus={true}
returnKeyType="next"
onChangeText={(text) => {
this.setState({firstName: text}, () => { this.validateForm('firstName'); });
onChangeText={(firstName) => {
this.setState({firstName}, () => { this.validateForm('firstName'); });
}}
onSubmitEditing={() => {
this.refs.lastNameInput._root.focus()
}}
value={this.state.firstName}
/>
{this.fieldHasError('firstName') && <Icon name='close-circle' />}
</Item>;
@@ -67,12 +72,13 @@ export default class UserRegister extends Component {
<Input
ref="lastNameInput"
returnKeyType="next"
onChangeText={(text) => {
this.setState({lastName: text}, () => { this.validateForm('lastName'); });
onChangeText={(lastName) => {
this.setState({lastName}, () => { this.validateForm('lastName'); });
}}
onSubmitEditing={() => {
this.refs.emailInput._root.focus()
}}
value={this.state.lastName}
/>
{this.fieldHasError('lastName') && <Icon name='close-circle' />}
</Item>;
@@ -87,12 +93,13 @@ export default class UserRegister extends Component {
autoCapitalize="none"
autoCorrect={false}
returnKeyType="next"
onChangeText={(text) => {
this.setState({email: text}, () => { this.validateForm('email'); });
onChangeText={(email) => {
this.setState({email}, () => { this.validateForm('email'); });
}}
onSubmitEditing={() => {
this.refs.phoneInput._root.focus()
}}
value={this.state.email}
/>
{this.fieldHasError('email') && <Icon name='close-circle' />}
</Item>;
@@ -105,12 +112,13 @@ export default class UserRegister extends Component {
ref="phoneInput"
returnKeyType="next"
keyboardType="phone-pad"
onChangeText={(text) => {
this.setState({phoneNumber: text}, () => { this.validateForm('phoneNumber'); });
onChangeText={(phoneNumber) => {
this.setState({phoneNumber}, () => { this.validateForm('phoneNumber'); });
}}
onSubmitEditing={() => {
this.refs.passwordInput._root.focus()
}}
value={this.state.phoneNumber}
/>
{this.fieldHasError('phoneNumber') && <Icon name='close-circle' />}
</Item>;
@@ -124,26 +132,38 @@ export default class UserRegister extends Component {
secureTextEntry={true}
autoCapitalize="none"
autoCorrect={false}
onChangeText={(text) => {
this.setState({password: text}, () => { this.validateForm('password'); });
onChangeText={(password) => {
this.setState({password}, () => { this.validateForm('password'); });
}}
onSubmitEditing={this.onRegisterPress}
value={this.state.password}
/>
{this.fieldHasError('password') && <Icon name='close-circle' />}
</Item>
}
showError = () => {
// TODO: Move these from prop to state in componentWillRecieveProps
// TODO: Format these and highlight error fields
if (this.props.registerError && Object.keys(this.props.registerError).length !== 0) {
const err = this.props.registerError.non_field_errors;
componentWillReceiveProps(nextProps) {
if(this.props.registerError !== nextProps.registerError) {
this.setState({'errors': nextProps.registerError})
}
}
showNonFieldError = () => {
const errors = this.props.registerError;
if (Object.keys(errors).length !== 0 && 'non_field_errors' in errors) {
const err = errors.non_field_errors;
let errorFormatted = err ? err[0] : 'Kunne ikke registrere bruker, prøv igjen...';
return <View style={styles.errorBox}><Text style={styles.errorMessage}>{errorFormatted}</Text></View>
}
else {
return <View style={styles.errorBox}/>
return <View style={styles.errorBox}/>
};
showFieldError = (field) => {
if(!this.fieldHasError(field) ) {
return;
}
return <Item style={styles.errorText}><Text style={styles.errorMessage}>{this.state.errors[field]}</Text></Item>;
};
showSpinner = () => {
@@ -158,11 +178,16 @@ export default class UserRegister extends Component {
<Content keyboardShouldPersistTaps='always'>
<Form style={styles.card}>
{this.firstNameInput()}
{this.showFieldError('firstName')}
{this.lastNameInput()}
{this.showFieldError('lastName')}
{this.emailInput()}
{this.showFieldError('email')}
{this.phoneNumberInput()}
{this.showFieldError('phoneNumber')}
{this.passwordInput()}
{this.showError()}
{this.showFieldError('password')}
{this.showNonFieldError()}
{this.registerbutton()}
{this.showSpinner()}
</Form>
@@ -172,7 +197,7 @@ export default class UserRegister extends Component {
}
onRegisterPress = () => {
if( !this.validateForm() ) {
if( !this.canSubmitForm() ) {
Toast.show({
text: 'Noen av feltene er ikke fylt ut riktig',
position: 'bottom',
@@ -192,7 +217,7 @@ export default class UserRegister extends Component {
};
registerbutton() {
const isDisabled = this.props.isRegisteringUser || !this.canSubmitForm();
const isDisabled = this.state.touched.size === 0 || this.props.isRegisteringUser || !this.canSubmitForm();
return (<Button
full
@@ -222,7 +247,8 @@ const styles = StyleSheet.create({
},
errorMessage: {
textAlign: 'center',
color: 'red',
color: platform.btnDangerBg,
fontSize: 14,
},
card: {
borderColor: '#e1e8ee',
@@ -242,4 +268,8 @@ const styles = StyleSheet.create({
margin: 8,
backgroundColor: '#fff',
},
errorText: {
paddingTop: 2,
paddingBottom: 12,
}
});
+12 -14
View File
@@ -9,8 +9,6 @@ class UserRegisterContainer extends Component {
super(state);
this.state = {
isAuthenticated: false,
isRegisteringUser: false,
registerError: null
}
}
@@ -20,12 +18,6 @@ class UserRegisterContainer extends Component {
this.props.onRegister();
}
}
if (this.state.isRegisteringUser !== nextProps.isRegisteringUser) {
this.setState({isRegisteringUser: nextProps.isRegisteringUser});
}
if (this.state.registerError !== nextProps.registerError) {
this.setState({registerError: nextProps.registerError});
}
}
onRegisterPress = (firstName, lastName, email, phoneNumber, password) => {
@@ -33,18 +25,24 @@ class UserRegisterContainer extends Component {
};
render() {
return <UserRegister {...this.state} onRegisterPress={this.onRegisterPress} />;
return <UserRegister
{...this.state}
onRegisterPress={this.onRegisterPress}
registerError={this.props.registerError}
isRegisteringUser={this.props.isRegisteringUser}
/>;
}
}
function stateToProps(state) {
function storeToProps(store) {
const errors = store.registerError === null ? {} : store.registerError;
return {
isAuthenticated: state.isAuthenticated,
isRegisteringUser: state.isRegisteringUser,
registerError: state.registerError
isAuthenticated: store.isAuthenticated,
isRegisteringUser: store.isRegisteringUser,
registerError: errors
};
}
export default connect(
stateToProps,
storeToProps,
{requestRegisterUser}
)(UserRegisterContainer);
+14 -1
View File
@@ -11,6 +11,8 @@ import {
REGISTER_USER_SUCCESS, MEMBERSHIP_CHARGE_REQUEST, MEMBERSHIP_CHARGE_FAILURE, MEMBERSHIP_CHARGE_SUCCESS,
} from './actions';
import { snakeToCamelCase } from './utils'
const initialState = {
isAuthenticated: false,
isLoggingIn: false,
@@ -26,6 +28,17 @@ const initialState = {
lastOrder: null,
};
function formatErrors(errs) {
let _errs = {};
for(let [key, value] of Object.entries(errs)) {
_errs[snakeToCamelCase(key)] = value.join('\n');
}
return _errs
}
export default function duskenApp(state = initialState, action) {
switch (action.type) {
/* Login */
@@ -80,7 +93,7 @@ export default function duskenApp(state = initialState, action) {
case REGISTER_USER_FAILURE:
return Object.assign({}, state, {
isRegisteringUser: false,
registerError: action.registerError
registerError: formatErrors(action.registerError)
});
case REGISTER_USER_SUCCESS:
+3
View File
@@ -0,0 +1,3 @@
export function snakeToCamelCase(val) {
return val.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); });
}