Building a Finance Management App with React Native - Part 4
Revising the Sign-Up Process
Today, I finalize the sign-up process. I'll be improving the user experience in these ways:
Separate the onboarding screens and the signup screens. The user will be able to view three onboarding screens and undergo a step-by-step registration process. I also added a skip button on the onboarding screens whereby the user can click on it and skip to the step-by-step registration process.
Get rid of the progress dots and replace them with "Step 1/3" statements at the top-right corner of the screen.
Github Link: https://github.com/faithgaiciumia/FinMan-App
Final Output: https://www.youtube.com/shorts/3pvqFaGcvko
New Onboarding Screens
I simply included text in the three sliding screens. Each screen describes a feature of the app. On the final screen, there is a get started button which takes the user to the signup page using react native navigation: - the same code is used for the skip button which is on every onboarding screen.
<TouchableOpacity style={styles.button} onPress {()=>navigation.replace("signup") }>
<LongButton text="Get Started" />
</TouchableOpacity>
The Signup Page
The signup page displays the different screens and their inputs conditionally. I used three boolean variables to conditionally render the screens. The boolean variables include stepOne, stepTwo, stepThree. Initially, only stepOne is true while the rest are false. Each screen is only displayed if its respective step variable is true. If the validation check for a screen is correct, the current step is set to false and the next step is set to true. Additionally, I declare and initialize all the input variables, their set functions, the error messages and their set functions on the signup page. These variables are then passed on to the respective screens.
Signup.js
import { Text, View, StyleSheet, Dimensions } from "react-native";
import SignUpOne from "./SignUpOne";
import SignUpTwo from "./SignUpTwo";
import SignUpThree from "./SignUpThree";
import React from "react";
export default function SignUp({ navigation }) {
//initiliaze formdata variables
const [email, setEmail] = React.useState("");
const [fullName, setFullName] = React.useState("");
const [password, setPassword] = React.useState("");
const [confirmPass, setConfirmPass] = React.useState("");
//initialise error messages
const [emailErr, setEmailErr] = React.useState(null);
const [fullNameErr, setFullNameErr] = React.useState(null);
const [passwordErr, setPasswordErr] = React.useState(null);
const [confirmPassErr, setConfirmPassErr] = React.useState(null);
//initialise conditional rendering variables
const [stepOne, setStepOne] = React.useState(true);
const [stepTwo, setStepTwo] = React.useState(false);
const [stepThree, setStepThree] = React.useState(false);
const [step, setStep] = React.useState(1);
return (
<View style={styles.container}>
{stepOne && (
<SignUpOne
fullName={fullName}
setFullName={setFullName}
fullNameErr={fullNameErr}
setFullNameErr={setFullNameErr}
setStepOne={setStepOne}
setStepTwo={setStepTwo}
step={step}
setStep={setStep}
/>
)}
{stepTwo && (
<SignUpTwo
email={email}
setEmail={setEmail}
emailErr={emailErr}
setEmailErr={setEmailErr}
setStepTwo={setStepTwo}
setStepThree={setStepThree}
step={step}
setStep={setStep}
/>
)}
{stepThree && (
<SignUpThree
password={password}
setPassword={setPassword}
confirmPass={confirmPass}
setConfirmPass={setConfirmPass}
confirmPassErr={confirmPassErr}
setConfirmPassErr={setConfirmPassErr}
passwordErr={passwordErr}
setPasswordErr={setPasswordErr}
setStepThree={setStepThree}
step={step}
setStep={setStep}
navigation={navigation}
/>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "flex-start",
alignItems: "flex-start",
alignContent: "center",
backgroundColor: "#fff",
},
onbImg: {
width: Dimensions.get("screen").width,
height: 300,
},
heading: {
marginTop: 0,
fontFamily: "PoppinsBold",
fontSize: 24,
width: "30%",
marginHorizontal: 20,
},
text: {
margin: 20,
fontFamily: "PoppinsMedium",
fontSize: 12,
},
errorTxt: {
marginHorizontal: 20,
fontFamily: "PoppinsMedium",
fontSize: 12,
color: "red",
},
button: {
marginHorizontal: 20,
},
row: {
flexDirection: "row",
justifyContent: "space-between",
width: Dimensions.get("screen").width - 40,
marginHorizontal: 20,
marginTop: 40,
},
textInput: {
width: Dimensions.get("screen").width - 40,
marginHorizontal: 20,
marginTop: 5,
},
btnTxt: {
color: "#12B886",
fontFamily: "PoppinsMedium",
},
});
SignupOne.js
import { TextInput } from "@react-native-material/core";
import {
Dimensions,
Image,
KeyboardAvoidingView,
StyleSheet,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
View,
Keyboard,
} from "react-native";
import LongButton from "../../components/LongButton";
export default function SignUpOne({
fullName,
setFullName,
fullNameErr,
setFullNameErr,
setStepOne,
setStepTwo,
step,
setStep
}) {
const validateData = () => {
//check if it is empty
if (fullName === "") {
setFullNameErr("Name is required");
return;
}
//check length of name - should be at least 3 characters
if (fullName.length < 3) {
setFullNameErr(
"Name is too short. - It should be at least 3 characters."
);
return;
}
//move to the next step.
setStepOne(false);
setStep(2);
setStepTwo(true);
};
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View>
<Text style={styles.text}>Step {step}/3</Text>
<Text style={[styles.heading]}>Tell us your name</Text>
<TextInput
label="Name"
variant="outlined"
style={styles.textInput}
value={fullName}
onChangeText={(fullName) => setFullName(fullName)}
/>
{/* conditionally render the error message */}
{fullNameErr && <Text style={styles.errorTxt}>{fullNameErr}</Text>}
<View style={styles.row}>
<TouchableOpacity style={styles.button} onPress={validateData}>
<LongButton text="Save" />
</TouchableOpacity>
</View>
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "flex-start",
alignItems: "flex-start",
alignContent: "center",
backgroundColor: "#fff",
marginTop:50
},
onbImg: {
width: Dimensions.get("screen").width,
height: 300,
},
heading: {
marginTop: 0,
fontFamily: "PoppinsBold",
fontSize: 24,
width: "30%",
marginHorizontal: 20,
},
text: {
margin: 10,
fontFamily: "PoppinsMedium",
fontSize: 12,
textAlign:"right"
},
errorTxt: {
marginHorizontal: 20,
fontFamily: "PoppinsMedium",
fontSize: 12,
color: "red",
},
button: {
margin: 20,
},
row: {
flexDirection: "row",
justifyContent: "space-between",
width: Dimensions.get("screen").width - 40,
marginHorizontal: 20,
marginTop: 0,
},
textInput: {
width: Dimensions.get("screen").width - 40,
marginHorizontal: 20,
},
btnTxt: {
color: "#12B886",
fontFamily: "PoppinsMedium",
},
});
SignupTwo.js
import { TextInput } from "@react-native-material/core";
import {
Dimensions,
Image,
KeyboardAvoidingView,
StyleSheet,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
View,
Keyboard,
} from "react-native";
import LongButton from "../../components/LongButton";
export default function SignUpTwo({
email,
setEmail,
emailErr,
setEmailErr,
setStepTwo,
setStepThree,
step,
setStep
}) {
const validateData = () => {
let validEmail = /\S+@\S+\.\S+/.test(email);
//check if it is empty
if (email === "") {
setemailErr("Email is required");
return;
}
//check if it is a valid email
if (!validEmail) {
setEmailErr("The email is invalid");
return;
}
//move to the next step
setStepTwo(false);
setStep(3);
setStepThree(true);
};
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View>
<Text style={styles.text}>Step {step}/3</Text>
<Text style={[styles.heading]}>Tell us your email</Text>
<TextInput
label="Email"
variant="outlined"
style={styles.textInput}
value={email}
onChangeText={(email) => setEmail(email)}
/>
{/* conditionally render the error message */}
{emailErr && <Text style={styles.errorTxt}>{emailErr}</Text>}
<View style={styles.row}>
<TouchableOpacity style={styles.button} onPress={validateData}>
<LongButton text="Save" />
</TouchableOpacity>
</View>
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "flex-start",
alignItems: "flex-start",
alignContent: "center",
backgroundColor: "#fff",
marginTop: 50,
},
onbImg: {
width: Dimensions.get("screen").width,
height: 300,
},
heading: {
marginTop: 0,
fontFamily: "PoppinsBold",
fontSize: 24,
width: "30%",
marginHorizontal: 20,
},
text: {
margin: 20,
fontFamily: "PoppinsMedium",
fontSize: 12,
textAlign: "right",
},
errorTxt: {
marginHorizontal: 20,
fontFamily: "PoppinsMedium",
fontSize: 12,
color: "red",
},
button: {
margin: 20,
},
row: {
flexDirection: "row",
justifyContent: "space-between",
width: Dimensions.get("screen").width - 40,
marginHorizontal: 20,
marginTop: 0,
},
textInput: {
width: Dimensions.get("screen").width - 40,
marginHorizontal: 20,
},
btnTxt: {
color: "#12B886",
fontFamily: "PoppinsMedium",
},
});
SignupThree.js
I use secureTextEntry on the inputs to hide the passwords as the user types.
I have also included an eye icon button where the user can toggle the password's visibility.
If the passwords are valid, user is taken to the temporary home page. I use navigation.replace so that the user is restrained from going back to the signup pages.
import { IconButton, TextInput } from "@react-native-material/core";
import {
Dimensions,
Image,
KeyboardAvoidingView,
StyleSheet,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
View,
Keyboard,
} from "react-native";
import Icon from "@expo/vector-icons/MaterialCommunityIcons";
import LongButton from "../../components/LongButton";
import React from "react";
export default function SignUpThree({
password,
setPassword,
confirmPass,
setConfirmPass,
confirmPassErr,
setConfirmPassErr,
passwordErr,
setPasswordErr,
setStepThree,
step,
setStep,
navigation,
}) {
//initialize the show password toggles
const [hidePass, setHidePass] = React.useState(true);
const [hideConfirmPass, setHideConfirmPass] = React.useState(true);
const validateData = () => {
let strongPassword =
/(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9])(?=.{10,})/.test(
password
);
//password is empty
if (password === "") {
setPasswordErr("Password is required.");
return;
}
//check if passwords match
if (password !== confirmPass) {
setConfirmPassErr("Passwords do not match.");
return;
}
//check if it is a strong password
if (!strongPassword) {
setPasswordErr(
"Weak Password. Ensure it is more than 10 characters and has alphanumeric characters."
);
return;
}
//move to home page
setStepThree(false);
navigation.replace("Home");
};
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View>
<Text style={styles.text}>Step {step}/3</Text>
<Text style={[styles.heading]}>Set a Password</Text>
<TextInput
label="Password"
variant="outlined"
style={styles.textInput}
value={password}
onChangeText={(password) => setPassword(password)}
secureTextEntry={hidePass}
trailing={(props) => (
<IconButton
icon={(props) => <Icon name="eye" {...props} />}
{...props}
onPress={() => setHidePass(!hidePass)}
/>
)}
/>
{/* conditionally render the error message */}
{passwordErr && <Text style={styles.errorTxt}>{passwordErr}</Text>}
<TextInput
label="Confirm Password"
variant="outlined"
style={styles.textInput}
value={confirmPass}
onChangeText={(confirmPass) => setConfirmPass(confirmPass)}
secureTextEntry={hideConfirmPass}
trailing={(props) => (
<IconButton
icon={(props) => <Icon name="eye" {...props} />}
{...props}
onPress={() => setHideConfirmPass(!hideConfirmPass)}
/>
)}
/>
{/* conditionally render the error message */}
{confirmPassErr && (
<Text style={styles.errorTxt}>{confirmPassErr}</Text>
)}
<View style={styles.row}>
<TouchableOpacity style={styles.button} onPress={validateData}>
<LongButton text="Done" />
</TouchableOpacity>
</View>
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "flex-start",
alignItems: "flex-start",
alignContent: "center",
backgroundColor: "#fff",
marginTop: 50,
},
onbImg: {
width: Dimensions.get("screen").width,
height: 300,
},
heading: {
marginTop: 0,
fontFamily: "PoppinsBold",
fontSize: 24,
width: "30%",
marginHorizontal: 20,
},
text: {
margin: 20,
fontFamily: "PoppinsMedium",
fontSize: 12,
textAlign: "right",
},
errorTxt: {
marginHorizontal: 20,
fontFamily: "PoppinsMedium",
fontSize: 12,
color: "red",
},
button: {
marginHorizontal: 20,
},
row: {
flexDirection: "row",
justifyContent: "space-between",
width: Dimensions.get("screen").width - 40,
marginHorizontal: 20,
marginTop: 40,
},
textInput: {
width: Dimensions.get("screen").width - 40,
marginHorizontal: 20,
marginTop: 5,
},
btnTxt: {
color: "#12B886",
fontFamily: "PoppinsMedium",
},
});
Next up, I will design the home page and implement the design. Thank you for reading through.