I created a login form using react-native and I want to validate every fields but I don't know how to do it. I'm quite new to react-native so I want to ask anyone for help. Form validation should show error under following conditions:
Input form is empty
Email text isn't email form.
Password text does not satisfy the conditions above.
If Input form has errors the login button should be disabled.
If Input form doesn't have any errors, show alert to inform login
success
Sample image validation:
Here is my code:
import React from 'react';
import { StyleSheet, Text, View, Image, TextInput, Dimensions, ScrollView,
CheckBox, TouchableOpacity } from 'react-native';
import logo from './image/Logo.png'
const { width: WIDTH } = Dimensions.get('window')
export default class App extends React.Component {
constructor(){
super();
this.state={
check:false,
email: '',
};
this.validates = this.validates.bind(this);
}
CheckBoxText(){
this.setState({
check:!this.state.check,
})
}
validates = () => {
let text = this.state.email;
let emailError = this.state.emails;
let reg = /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/ ;
if(reg.test(text) === false)
{
console.warn("Invalid email")
this.setState({email:text})
return false;
}
else {
this.setState({email:text})
console.log("Email is Correct");
}
}
render() {
return (
<View>
<View style={styles.container}>
<Image source={logo} style={styles.logo}/>
</View>
<View style = {styles.container2}>
<Text style={styles.emailAdd}>
Email
</Text>
<TextInput
onChangeText={(text) => this.setState({email:text})}
type='email'
value={this.state.email}
keyboardType='email-address'
style={styles.emailInput}
placeholder={'Input Email Address'}
underlineColorAndroid='transparent'/>
</View>
<View style = {styles.container3}>
<Text style={styles.password}>
Password
</Text>
<TextInput
style={styles.passwordInput}
placeholder={'Input Password'}
secureTextEntry={true}
underlineColorAndroid='transparent'/>
</View>
<View style = {styles.container4}>
<View>
<CheckBox value={this.state.check} onChange={()=>this.CheckBoxText()} style={styles.rememberMe}/>
</View>
<View>
<Text style={styles.remember}>Remember me</Text>
</View>
</View>
<TouchableOpacity style={styles.btnLogin} onPress={this.validates} >
<Text style={styles.txtLogin}>Sign In</Text>
</TouchableOpacity>
</View>
);
}
}
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
var validRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
if (!email) {
Toast.show('Email is required.');
} else if (!email.match(validRegex)) {
Toast.show('Invalid Email');
} else if (!password) {
Toast.show('Password is required.');
}
I recommend using formik and yup to easily build a login form with input validation. These two packages when integrated together, simplifies your codebase thanks to both of its features.
Please take a look at a CodeSandbox snippet here, https://codesandbox.io/s/stack-overflow-54204827-llvkzc?file=/index.tsx:254-656. And note, I'm using typescript here.
The package.json file at the time of written snippet is:
"dependencies": {
...
"formik": "2.2.9",
...
"yup": "0.32.11"
},
And to break the solution down, first we define our yup schema for our Login form:
Note, you may tweak the regex pattern later, as this password validation accepts min 6 to max 12 characters, with at least one uppercase letter, one lowercase letter, one number and one special character.
/**
* The `yup` Login Form schema
*/
const LoginSchemaA = Yup.object().shape({
email: Yup.string()
.email("Invalid email.")
.required("Email must be provided."),
password: Yup.string()
.required("Password must be provided.")
.matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!##\$%\^&\*])(?=.{6,12})/,
"Password must be minimum 6 and maximum 12 characters."
)
});
Note, .email("Invalid email.") here is the default email validation feature used. You can remove this, and use .matches(...) function instead for your own regular expression.
And just the <Formik /> section for your further use:
<Formik
initialValues={{
email: "",
password: ""
}}
validationSchema={LoginSchemaA}
onSubmit={(
values: Values,
{ setSubmitting }: FormikHelpers<Values>
) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 500);
}}
>
{({ errors, touched }) => (
<Form>
<label htmlFor="email">Email</label>
<Field
id="email"
name="email"
placeholder="john.doe#email.com"
type="email"
/>
{errors.email && touched.email ? (
<div style={{ color: "red" }}>{errors.email}</div>
) : null}
<label htmlFor="password">Password</label>
<Field id="password" name="password" type="password" />
{errors.password && touched.password ? (
<div style={{ color: "red" }}>{errors.password}</div>
) : null}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
Lastly, you would want to grab the values itself for further use - ignore the setTimeout, alert and setSubmitting usages.
An example output of JSON.stringify(values, null, 2) would be as below:
{
"email": "john.doe#test.com",
"password": "Awesome#Password!2022"
}
Hope this helps you in your react-native coding journey!
here is my code you can try this
import React, { Component } from "react"
import { View, Button } from "react-native"
import TextField from "textfield"
import validation from "validation"
import validate from "validation_wrapper"
export default class Form extends Component {
constructor(props) {
super(props)
this.state = {
email: "",
emailError: "",
password: "",
passwordError: ""
}
}
register() {
const emailError = validate("email", this.state.email)
const passwordError = validate("password", this.state.password)
this.setState({
emailError: emailError,
passwordError: passwordError
})
if (!emailError && !passwordError) {
alert("Details are valid!")
}
}
render() {
return (
<View>
<TextField
onChangeText={(value) => this.setState({ email: value.trim() })}
onBlur={() => {
this.setState({
emailError: validate("email", this.state.email)
})
}}
error={this.state.emailError}
/>
<TextField
onChangeText={(value) => this.setState({ password: value.trim() })}
onBlur={() => {
this.setState({
passwordError: validate("password", this.state.password)
})
}}
error={this.state.passwordError}
secureTextEntry={true}
/>
<Button title="Register" onPress={this.validateRegister} />
</View>
)
}
}
<!-- begin snippet: js hide: false console: true babel: false -->
const validation = {
email: {
presence: {
message: "^Please enter an email address"
},
email: {
message: "^Please enter a valid email address"
}
},
password: {
presence: {
message: "^Please enter a password"
},
length: {
minimum: 5,
message: "^Your password must be at least 5 characters"
}
}
}
export default validation
import validation from "validation.js"
export default function validate(fieldName, value) {
// Validate.js validates your values as an object
// e.g. var form = {email: 'email#example.com'}
// Line 8-9 creates an object based on the field name and field value
var formValues = {}
formValues[fieldName] = value
// Line 13-14 creates an temporary form with the validation fields
// e.g. var formFields = {
// email: {
// presence: {
// message: 'Email is blank'
// }
// }
var formFields = {}
formFields[fieldName] = validation[field]
// The formValues and validated against the formFields
// the variable result hold the error messages of the field
const result = validatejs(formValues, formFields)
// If there is an error message, return it!
if (result) {
// Return only the field error message if there are multiple
return result[field][0]
}
return null
}
import React from "react"
import { View, TextInput, Text } from "react-native"
const TextField = (props) => (
<View>
<TextInput />
props.error ? <Text>{props.error}</Text> : null
</View>
)
export default TextField
Related
I'm trying to run a test on login form submit which goes through a Rest API call to authenticate a user.
I have configured MSW for mocking the rest API. Whenever I am running the npm test command the rest api call isn't going through and returning an error. Seems like the mock API isn't working in this case
I have configured MSW for mocking the rest API. I am attaching the handler file, jest setup file and screen file below for reference.
jest.setup.config
import mockAsyncStorage from '#react-native-async-storage/async-storage/jest/async-storage-mock';
import 'whatwg-fetch';
global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
jest.mock('#react-native-async-storage/async-storage', () => mockAsyncStorage);
login.screen.tsx
import React from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { Button, Pressable, SafeAreaView, Text, View } from 'react-native';
import styles from './EmailLogin.style';
import Input from '../../components/Input/Input.component';
import { color } from '../../theme';
import LinearGradient from 'react-native-linear-gradient';
import { setUser } from '../../stores/user.reducer';
import { useLoginUserMutation } from '../../services/api/auth';
import { useAppDispatch } from '../../hooks/redux';
const EMAIL_PATTERN = /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i;
interface IEmailFormInputs {
email: string;
password: string;
}
const EmailLogin: React.FC = ({ navigation }: any) => {
const dispatch = useAppDispatch();
const [loginUser, { isLoading, isError, error, isSuccess }] =
useLoginUserMutation();
const onSubmit: SubmitHandler<IEmailFormInputs> = async requestData => {
try {
const { result } = await loginUser(requestData).unwrap();
dispatch(setUser(result));
navigation.navigate('Home');
} catch (err) {
console.log(err);
}
};
const {
control,
handleSubmit,
formState: { errors },
} = useForm<IEmailFormInputs>({
mode: 'onChange',
defaultValues: {
email: '',
password: '',
},
});
return (
<SafeAreaView style={styles.whiteBackground}>
<View style={styles.mainContainer}>
<View style={styles.welcomeTextContainer}>
<Text style={styles.welcomeTextTitle}>HELLO,</Text>
<Text style={styles.welcomeTextSubTitle}>Welcome back</Text>
</View>
<View style={styles.inputContainer}>
<Input
name="email"
control={control}
rules={{
pattern: {
value: EMAIL_PATTERN,
message: 'Invalid email address',
},
}}
error={errors.email}
placeholder={'Email'}
autoCapitalize={'none'}
autoCorrect={false}
/>
</View>
<View style={styles.inputContainer}>
<Input
name="password"
control={control}
rules={{
minLength: {
value: 3,
message: 'Password should be minimum 3 characters long',
},
}}
secureTextEntry={true}
error={errors.password}
placeholder={'Password'}
autoCapitalize={'none'}
autoCorrect={false}
/>
</View>
<Pressable onPress={handleSubmit(onSubmit)}>
<LinearGradient
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
colors={color.gradient.primary}
style={styles.buttonContainer}>
<Text style={styles.buttonText}>Login</Text>
</LinearGradient>
</Pressable>
</View>
</SafeAreaView>
);
};
export default EmailLogin;
login.spec.js
import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '../../test-utils';
import { server } from '../../mocks/server';
import EmailLogin from './EmailLogin.screen';
describe('Home', () => {
//handles valid input submission
it('handle valid form submission', async () => {
await render(<EmailLogin />);
await act(async () => {
fireEvent(
screen.getByPlaceholderText('Email'),
'onChangeText',
'sample#email.com',
);
fireEvent(
screen.getByPlaceholderText('Password'),
'onChangeText',
'password',
);
fireEvent.press(screen.getByText('Login'));
//expected result on success login to be mentioned here
});
});
});
mocks/handler.js
import { rest } from 'msw';
import config from '../config';
export const handlers = [
rest.post(`*url*/user/auth`, (req, res, ctx) => {
console.log(req);
// successful response
return res(
ctx.status(200),
ctx.json({
success: true,
result: {
auth_token: ['authtoken'],
email: req.bodyUsed.email,
id: 1,
first_name: 'xxx',
last_name: 'xxx',
number: 'xxxxxxx',
user_state: 1,
phone_verified: true,
},
}),
);
}),
];
I get the following error when
console.log
{ status: 'FETCH_ERROR', error: 'AbortError: Aborted' }
at src/screens/EmailLogin/EmailLogin.screen.tsx:32:15
at Generator.throw (<anonymous>)
I'm new to react native.
I've created a react native app and my first screen is a login screen. I'm using onChangeText to update state vars with username and password and this works great initially.
However on "logout" when I pop back to the login screen. The inputs still have my username and password in. However the state vars are now back to null.
I've tried setting value to {this.state.username} for the input but this just causes a depth error on state after 2 input presses so doesn't work.
Am I missing something?
import React, { Component } from 'react';
import { View, Text, StyleSheet, Image, Alert, AsyncStorage, Linking } from 'react-native';
import { Input, Left, Spinner, Container, Item, Form, Header, Content, Label, Button } from 'native-base'
export default class Login extends Component {
state = { username: "", password: "", isLoaded: true }
static navigationOptions = {
header: null
}
constructor(props) {
super()
this.state.isLoaded = false
AsyncStorage.getItem("loggedIn").then(res => {
if (res === "true") {
this.props.navigation.navigate('List')
}
else {
this.setState({isLoaded: true})
}
})
}
checkLogin() {
if ((!this.state.username) || (!this.state.password)) {
Alert.alert('Error', 'Username/Password combination unknown', [{
text: 'Okay'
}])
return
}
....... snip ......
if (response === false) {
Alert.alert('Error', 'Username/Password combination unknown', [{
text: 'Okay'
}])
}
else {
AsyncStorage.setItem('user', JSON.stringify(response));
AsyncStorage.setItem('loggedIn', "true");
this.setState({username: null, password: null})
this.props.navigation.navigate('List')
}
}
}
render()
{
if (this.state.isLoaded == false) {
return (
<Container>
<Spinner />
</Container>
)
}
return (
<Container>
<Content>
<Image source={require('../../assets/logo.jpg')}/>
<Form>
<Item floatingLabel>
<Label>Username</Label>
<Input
autoCapitalize='none'
clearButtonMode='always'
onChangeText={text => this.setState({username:text})} />
</Item>
<Item floatingLabel>
<Label>Password</Label>
<Input
secureTextEntry={true}
clearButtonMode='always'
onChangeText={text => this.setState({password: text})} />
</Item>
<Button primary onPress={_ => this.checkLogin()}>
<Text style={styles.loginButtonText}>Login</Text>
</Button>
</Form>
</Content>
</Container>
);
}
}
You can use direct manipulation method.
Try passing ref to Input like ref={ (c) => this._input = c } and then calling the setNativeProps function this._input.setNativeProps({text:''})
I am also using react navigation and face similar issue.
I fixed as below :
import { NavigationEvents } from "react-navigation";
class ... {
onStartScreenFocus = ()>={
this.setState({
username: "", password: ""
})
}
render(){
return(
<View>
<NavigationEvents
onWillFocus={() => this.onStartScreenFocus()}
onDidBlur={() => this.onDidScreenBlur()} />
<View>
)
}
}
Is there a way to limit the textinput between a minimum length and maximum length. Suppose I want to limit the textinput length between 5 and 15, how do I do that ?
Consider adding the following code in your component:
<TextInput onTextChange={this.onTextChange} maxLength={15} ... />
<Button onPress={this.onPress} ... >Submit</Button>
onTextChange = text => {
this.setState({text : text});
}
onPress = () => {
const {text} = this.state;
if(text.length < 5) {
console.log('Your text is less than what is required.');
}
}
You can do it using redux-form, following below steps
we.js
module.exports = {
reqMsg: 'Required',
maxLength: max => value => value && value.length > max ? `Must be ${max} characters or less` : undefined,
minValue: min => value => value && value.length < min ? `Must be at least ${min} characters` : undefined,
};
validations.js
import { reqMsg, maxLength, minValue } from './we';
module.exports = {
//Validation
required: value => value ? undefined : reqMsg,
maxLength15: maxLength(15),
minValue5: minValue(5)
};
UserCreateForm.js
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { Item, Input, CheckBox, ListItem, Spinner, Icon } from 'native-base';
import { required, minValue5, maxLength15} from './validations';
const renderField = ({ secureTextEntry, iconType, iconName, keyboardType, placeholder, meta: { touched, error, warning }, input: { onChange, ...restInput } }) => {
return (
<View>
<Item error={touched && !!error} rounded>
<Icon type={iconType} name={iconName} />
<Input secureTpickerStyleextEntry={JSON.parse(secureTextEntry)} keyboardType={keyboardType}
onChangeText={onChange} {...restInput} placeholder={placeholder} autoCapitalize='none'>
</Input>
{touched && !!error && <Icon name='close-circle' />}
</Item>
{touched && (!!error && <Text>{error}</Text>)}
</View>
);
};
class UserComponent extends Component {
render() {
return (
<Field name="Name" iconType="SimpleLineIcons" iconName="user" secureTextEntry="false" keyboardType="default" placeholder="FirstName LastName NikeName" component={renderField}
validate={[required, minValue5, maxLength15]}
/>
);
}
}
const UserCreateForm = reduxForm({
form: USER_CREATE_FORM // a unique identifier for this form
})(UserComponent);
export default UserCreateForm;
Previous comment is also Good, but it have more time and space complexity. For this overcome use this code .
<TextInput onTextChange={this.onTextChange} maxLength={15} ... />
onTextChange=()=>{
if (value ==^[a-zA-Z0-9]{5,15}$) {
alert( "Input is valid\n");
} else {
alert( "Input is invalid\n");
}
}
this code help me use this code, you can also reset the limit length, change the value
here 5 :- minimum
15:- maximum value.
I have form with only one TextInput which is made using redux-form. I am checking (!meta.active) to show validation message, since focus is not changing even on button click from TextInput, meta.active is always true and validation message does not shows up.
export default function MTTextInput(props) {
const { input, label, meta, ...inputProps } = props;
var hasError = false;
if (meta.error !== undefined && meta.touched && !meta.active) {
hasError = true;
}
return (
<Item fixedLabel error={hasError} ><Label>{label}</Label>
<Input
{...inputProps}
onChangeText={input.onChange}
onBlur={input.onBlur}
onFocus={input.onFocus}
value={input.value}
/>
{hasError ? <Text>{meta.error}</Text> : <Text />}
</Item>
);
}
MTTextInput.propTypes = {
input: PropTypes.shape({
onBlur: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
value: PropTypes.any.isRequired
}).isRequired,
meta: PropTypes.shape({
active: PropTypes.bool.isRequired,
error: PropTypes.string,
invalid: PropTypes.bool.isRequired,
pristine: PropTypes.bool.isRequired,
visited: PropTypes.bool.isRequired
}).isRequired
};
Perhaps you may want to switch from an <Input/> component to a <TextInput/> component. Here is a generic example that you can find here:
import React from 'react';
import { TextInput, View, Text } from 'react-native';
/**
* to be wrapped with redux-form Field component
*/
export default function MyTextInput(props) {
const { input, meta, ...inputProps } = props;
const formStates = ['active', 'autofilled', 'asyncValidating', 'dirty', 'invalid', 'pristine',
'submitting', 'touched', 'valid', 'visited'];
return (
<View>
<TextInput
{...inputProps}
onChangeText={input.onChange}
onBlur={input.onBlur}
onFocus={input.onFocus}
value={input.value}
/>
<Text>The { input.name} input is:</Text>
{
formStates.filter((state) => meta[state]).map((state) => {
return <Text key={state}> - { state }</Text>;
})
}
</View>
);
}
I have a simple login screen that asks for an email and password.
Login Screen
If the "Sign In" button is pressed and both of the fields are blank I get this error: "null is not an object (evaluating'_this.state.Email')"
Error Screen
Here is the code:
import React, {Component} from 'react';
import {View, Button, ScrollView, AsyncStorage, Alert } from 'react-native';
import colors from '../config/colors';
import { TextInput } from '../components/TextInput';
class SignIn extends Component {
signIn = () => {
const {Email} = this.state;
const {Password} = this.state;
fetch('http://192.168.1.3/Restaurant_App/php/sign_in.php', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application.json',
},
body: JSON.stringify({
email: Email,
password: Password,
})
}).then((response) => response.json())
.then((responseJson) => {
if (responseJson == Email) {
Alert.alert(responseJson);
AsyncStorage.setItem('email', Email);
this.props.navigation.navigate('Search');
} else {
Alert.alert(responseJson);
}
}).catch((error) => {
console.error(error);
});
};
render() {
return (
<View>
<ScrollView style={{ backgroundColor: colors.background }}>
<TextInput
placeholder="Email..."
onChangeText={Email => this.setState({Email})}
/>
<TextInput
placeholder="Password..."
secureTextEntry={true}
onChangeText={Password => this.setState({Password})}
/>
</ScrollView>
<Button
onPress={() => this.signIn()}
title="Sign In"
/>
</View>
);
}
}
export default SignIn;
I would like it to be so that if the "Sign In" button is pressed with empty fields, I won't get this error. Instead, there should be an alert saying "Please fill in all fields." or something like that.
You should do some validation checks before making the fetch request.
You could do something like this
signIn = () => {
const {Email, Password} = this.state;
if(!this.checkDetails(Email, Password) {
// you could show an alert here, but it is not great UX,
// you should show your user where they have gone wrong,
// by making style changes, a red border around the TextInput,
// text explaining what has gone wrong.
return;
}
fetch('http://192.168.1.3/Restaurant_App/php/sign_in.php', {
...
}).then((response) => response.json())
.then((responseJson) => {
...
}).catch((error) => {
console.error(error);
});
};
checkDetails = (Email, Password) => {
// check that it is a valid email address
// this is a regex that I have used in the past to check email addresses.
const emailIsValid = /^(([^<>()\[\]\\.,;:\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,}))$/.test(Email);
// if the password has a minimum length then you should use that rather than 0
this.setState({emailIsValid, passwordIsValid: Password.length > 0});
if (emailIsValid && Password.length > 0) return true;
return false;
}
Using these new state values for the email and password being valid you could set additional styles and error text beside the fields that are wrong or missing.
<TextInput
placeholder="Email..."
onChangeText={Email => this.setState({Email})}
styles={this.state.emailIsValid ? styles.validEmail : styles.invalidEmail}
/>
{!this.state.emailIsValid && <Text>Please input a valid email</Text>}
<TextInput
placeholder="Password..."
secureTextEntry={true}
onChangeText={Password => this.setState({Password})}
styles={this.state.passwordIsValid ? styles.validPassword : styles.invalidPassword}
/>
{!this.state.passwordIsValid && <Text>Please input a valid password</Text>}
Don't for get to set up your styles for the different states.
const styles = StyleSheet.create({
validEmail: {},
validPassword: {},
invalidEmail: {},
invalidPassword: {}
});
You'll probably want to add initial state values for the emailIsValid and passwordIsValid so that they are set to true so that the correct styles are shown. Also you should define initial state for the Email and Password.
Add a constructor to your class
constructor (props) {
super(props);
this.state = {
Email: '',
Password: '',
emailIsValid: true,
passwordIsValid: true
}
}
I hope that this helps.
You can do at the top of your sign in function something like this:
If(this.state.email.length === 0 || this.state.password.length === 0) {
alert(“please complete the fields”);
return;}