Updated October 23, 2019
Improved Form Error Messages using React Native Animatable and Hooks
Terminal
yarn add react-native-animatable
App.js
// Design based on: https://dribbble.com/shots/6803035-TimeNote-Login
import React, { useState, useRef, useEffect } from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
TouchableOpacity,
KeyboardAvoidingView,
} from 'react-native';
import * as Animatable from 'react-native-animatable';
const Error = ({ display = false }) => {
const viewElement = useRef(null);
useEffect(() => {
if (display) {
viewElement.current.animate('shake', 500, 'linear');
} else {
viewElement.current.animate('bounceOut', 500);
}
}, [display]);
const viewStyles = [styles.error, { opacity: 0 }];
if (display) {
viewStyles.push({ opacity: 1 });
}
return (
<Animatable.View style={viewStyles} ref={viewElement}>
<Text style={styles.errorText}>X</Text>
</Animatable.View>
);
};
const Input = ({ label, error, ...props }) => {
return (
<View style={styles.inputContainer}>
<Text style={styles.inputLabel}>{label}</Text>
<View style={styles.row}>
<TextInput autoCapitalize="none" style={styles.input} {...props} />
<Error display={error} />
</View>
</View>
);
};
const Button = ({ text, onPress }) => {
return (
<TouchableOpacity onPress={onPress} style={styles.button}>
<Text style={styles.buttonText}>{text}</Text>
</TouchableOpacity>
);
};
const useLoginFormState = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [submit, setSubmit] = useState(false);
let isUsernameValid = false;
let isPasswordValid = false;
if (username === 'example') {
isUsernameValid = true;
}
if (password === 'asdf') {
isPasswordValid = true;
}
return {
username: {
value: username,
set: setUsername,
valid: isUsernameValid,
},
password: {
value: password,
set: setPassword,
valid: isPasswordValid,
},
submit: {
value: submit,
set: () => setSubmit(true),
},
};
};
export default () => {
const { username, password, submit } = useLoginFormState();
return (
<View style={styles.container}>
<KeyboardAvoidingView behavior="position" keyboardVerticalOffset="25">
<Text style={styles.headerText}>Login</Text>
<Input
label="Username"
placeholder="example"
onChangeText={username.set}
error={submit.value && !username.valid}
/>
<Input
label="Password"
placeholder="***"
secureTextEntry
onChangeText={password.set}
error={submit.value && !password.valid}
/>
<Button text="Login" onPress={submit.set} />
</KeyboardAvoidingView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
justifyContent: 'center',
paddingHorizontal: 40,
},
headerText: {
color: '#353031',
fontWeight: 'bold',
fontSize: 34,
marginBottom: 10,
},
inputContainer: {
backgroundColor: '#f4f6f8',
paddingVertical: 10,
paddingHorizontal: 15,
borderRadius: 10,
marginVertical: 5,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
},
inputLabel: {
fontSize: 10,
color: '#b4b6b8',
},
input: {
color: '#353031',
fontWeight: 'bold',
fontSize: 14,
marginTop: 3,
marginRight: 10,
flex: 1,
},
error: {
backgroundColor: '#cc0011',
width: 20,
height: 20,
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
},
errorText: {
color: '#fff',
fontSize: 10,
fontWeight: 'bold',
},
button: {
backgroundColor: '#9374b7',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 16,
borderRadius: 10,
marginTop: 10,
},
buttonText: {
color: '#fff',
fontSize: 14,
fontWeight: 'bold',
},
});