Profile Creation Form

A comprehensive user registration form with floating label inputs, real-time validation, and secure password visibility toggle for seamless profile creation.

react-nativeexposignup-formregistrationfloating-labelsform-validationpassword-toggleuser-inputprofile-creationui-componentkeyboard-avoiding

Step 1: Install Dependencies

Expo Icons you can skip this if you want you use your own icons

npx expo install @expo/vector-icons/Ionicons

Step 2: Add the FloatingTextInput from Components

Usage

SignupFormPage.tsx
import React, { useMemo, useState } from "react";
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  TouchableOpacity,
  SafeAreaView,
  KeyboardAvoidingView,
  Platform,
  Alert,
} from "react-native";
import Ionicons from "@expo/vector-icons/Ionicons";
import FloatingTextInput from "@/components/ui/FloatingTextInput";
import { useAppColors } from "@/hooks/useAppColors"; 

// Regex and messages
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{9,}$/; 
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
const passwordRequirementMessage =
  "Min. 9 chars: 1 uppercase, 1 lowercase, 1 number, 1 special char.";
const REQUIRED_FIELD_MESSAGE = "This field is required.";
const INVALID_EMAIL_MESSAGE = "Please enter a valid email address.";

// Define form fields for easier management and animation staggering
const FORM_FIELDS_CONFIG = [
  { name: "fullName", label: "Full Name", keyboardType: "default" as const },
  { name: "email", label: "Email Address", keyboardType: "email-address" as const },
  { name: "address", label: "Address", keyboardType: "default" as const },
];

type FormData = {
  fullName: string;
  email: string;
  address: string;
  password: string;
};

type FormErrors = {
  fullName?: string;
  email?: string;
  address?: string;
  password?: string;
};

export default function SignupFormPage() {
  const colors = useAppColors();

  const [formData, setFormData] = useState<FormData>({
      fullName: "",
      email: "",
      address: "",
      password: "",
  });
  const [formErrors, setFormErrors] = useState<FormErrors>({});

  const [showPassword, setShowPassword] = useState(false);
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = (field: keyof FormData, value: string) => {
      setFormData((prev) => ({ ...prev, [field]: value }));
      if (formErrors[field]) {
          setFormErrors((prev) => ({ ...prev, [field]: undefined }));
      }
  };

  const validateForm = () => {
      const errors: FormErrors = {};
      let isValid = true;

      FORM_FIELDS_CONFIG.forEach(fieldConfig => {
          if (!formData[fieldConfig.name as keyof FormData].trim()) {
              errors[fieldConfig.name as keyof FormErrors] = REQUIRED_FIELD_MESSAGE;
              isValid = false;
          }
      });
      
      if (formData.email.trim() && !emailRegex.test(formData.email)) {
          errors.email = INVALID_EMAIL_MESSAGE;
          isValid = false;
      }

      if (!formData.password.trim()) {
          errors.password = REQUIRED_FIELD_MESSAGE;
          isValid = false;
      } else if (!passwordRegex.test(formData.password)) {
          errors.password = passwordRequirementMessage;
          isValid = false;
      }
      
      setFormErrors(errors);
      return isValid;
  };

  const handleSubmit = async () => {
      if (!validateForm()) {
          return;
      }
      setIsSubmitting(true);
      try {
          // Simulate API call
          await new Promise(resolve => setTimeout(resolve, 1500));

          console.log("Form submitted:", { ...formData });
          Alert.alert("Profile Created!", "Your profile has been successfully created.", [
              { text: "OK" } // Example navigation
          ]);
          // Optionally reset form:
          setFormData({ fullName: "", email: "", address: "" , password: ""});
          setFormErrors({});
      } catch (error) {
          console.error("Submission error:", error);
          Alert.alert("Error", "Could not create profile. Please try again.");
      } finally {
          setIsSubmitting(false);
      }
  };
  
  const isSubmitDisabled = useMemo(() => {
      // any field empty or password empty implies invalid to disable button quickly
      if (Object.values(formData).some(value => !value.trim()) ){
           return true;
      }
      // Or if currently submitting
      return isSubmitting;
  }, [formData, isSubmitting]);


  return (
      <SafeAreaView style={[styles.safeArea, { backgroundColor: colors.Neutral0 }]}>
          <KeyboardAvoidingView
              behavior={Platform.OS === "ios" ? "padding" : "height"}
              style={styles.keyboardAvoid}
          >
              <ScrollView
                  contentContainerStyle={styles.scrollContainer}
                  showsVerticalScrollIndicator={false}
                  keyboardShouldPersistTaps="handled"
              >
                  <View style={[styles.headerContainer]}>
                      <Text style={[styles.headerTitle, { color: colors.Neutral900 }]}>
                          Create Your Profile
                      </Text>
                      <Text style={[styles.headerSubtitle, { color: colors.Neutral500 }]}>
                          Please fill in your information to get started
                      </Text>
                  </View>

                  <View style={styles.formContainer}>
                      {FORM_FIELDS_CONFIG.map((field, index) => (
                          <View
                              key={field.name}
                              style={[
                                  styles.inputWrapper,
                                  
                              ]}
                          >
                              <FloatingTextInput
                                  label={field.label}
                                  value={formData[field.name as keyof FormData]}
                                  valueColor={colors.Neutral700}
                                  onChangeText={(text) =>
                                      handleChange(field.name as keyof FormData, text)
                                  }
                                  backgroundColor={colors.Neutral0}
                                  keyboardType={field.keyboardType}
                                  isError={!!formErrors[field.name as keyof FormErrors]}
                                  isBlurBorderColor={colors.Neutral100}
                                  isBlurValueBorderColor={colors.Neutral300}
                                  errorMessage={formErrors[field.name as keyof FormErrors]}
                                  isFocusBorderColor={colors.PrimaryNormal}
                                  isBlurLabelColor={colors.Neutral500}
                                  reduceMotion="never"
                                  isFocusLabelColor={colors.PrimaryNormal}
                              />
                          </View>
                      ))}

                      <View style={[styles.inputWrapper]}>
                          <FloatingTextInput
                              label="Password"
                              valueColor={colors.Neutral700}
                              value={formData['password']}
                              onChangeText={(text) =>
                                  handleChange('password', text)
                              }
                              backgroundColor={colors.Neutral0}
                              secureTextEntry={!showPassword}
                              clearButtonMode="never"
                              reduceMotion="never"
                              isError={!!formErrors.password}
                              isBlurBorderColor={colors.Neutral100}
                              isBlurValueBorderColor={colors.Neutral300}
                              errorMessage={formErrors.password}
                              isFocusBorderColor={colors.PrimaryNormal}
                              isBlurLabelColor={colors.Neutral500}
                              isFocusLabelColor={colors.PrimaryNormal}
                          />
                          <TouchableOpacity
                              style={styles.eyeIcon}
                              onPress={() => setShowPassword(!showPassword)}
                              activeOpacity={0.7}
                          >
                              <Ionicons
                                  name={showPassword ? "eye-off-outline" : "eye-outline"}
                                  size={24}
                                  color={colors.Neutral700}
                              />
                          </TouchableOpacity>
                      </View>

                      <View style={ { opacity: isSubmitting ? 0.7 : 1 }}>
                          <TouchableOpacity
                              style={[
                                  styles.submitButton,
                                  { backgroundColor: colors.PrimaryNormal },
                                  (isSubmitDisabled || isSubmitting) && styles.submitButtonDisabled,
                              ]}
                              onPress={handleSubmit}
                              activeOpacity={0.8} // Handled by animated scale mostly
                              disabled={isSubmitDisabled || isSubmitting}
                          >
                              <Text style={[styles.submitButtonText, { color: colors.Neutral0 }]}>
                                  {isSubmitting ? "Creating Profile..." : "Create Profile"}
                              </Text>
                          </TouchableOpacity>
                      </View>
                      <View style={{ height: Platform.OS === 'ios' ? 100 : 60 }} />
                  </View>
              </ScrollView>
          </KeyboardAvoidingView>
      </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
      flex: 1,
  },
  keyboardAvoid: {
      flex: 1,
  },
  scrollContainer: {
      flexGrow: 1,
      paddingHorizontal: 24,
      paddingTop: Platform.OS === 'ios' ? 20 : 30, // More padding at the top
      paddingBottom: 20,
  },
  headerContainer: {
      marginBottom: 32,
      alignItems: "center",
  },
  headerTitle: {
      fontSize: 28,
      fontWeight: "bold",
      marginBottom: 8,
      textAlign: "center",
  },
  headerSubtitle: {
      fontSize: 16,
      lineHeight: 24,
      textAlign: "center",
  },
  formContainer: {
      width: "100%",
  },
  inputWrapper: {
      marginBottom: 16,
  },
  eyeIcon: {
      position: "absolute",
      right: 16,
      top: 20 + 50 / 2 - 12, 
      zIndex: 2,
  },
  submitButton: {
      paddingVertical: 16,
      borderRadius: 12,
      alignItems: "center",
      marginTop: 24,
  },
  submitButtonDisabled: {
      backgroundColor: "#D1B999",
      opacity: 0.7, 
  },
  submitButtonText: {
      fontSize: 17,
      fontWeight: "600",
  },
});