Simple Sign Up Form

Screen is created using these React Native Core Components: <SafeAreaView />, <View />, <TouchableOpacity />, <Text />, <TextInput />

import React, { useState } from 'react';
import {
  StyleSheet,
  SafeAreaView,
  View,
  TouchableOpacity,
  Text,
  TextInput,
} from 'react-native';
import FeatherIcon from 'react-native-vector-icons/Feather';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';

export default function Example() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    password: '',
    confirmPassword: '',
  });
  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: '#e8ecf4' }}>
      <View style={styles.container}>
        <View style={styles.header}>
          <TouchableOpacity
            onPress={() => {
              // handle onPress
            }}
            style={styles.headerBack}>
            <FeatherIcon
              color="#1D2A32"
              name="chevron-left"
              size={30} />
          </TouchableOpacity>
        </View>

        <Text style={styles.title}>Let's Get Started!</Text>

        <Text style={styles.subtitle}>
          Fill in the fields below to get started with your new account.
        </Text>

        <KeyboardAwareScrollView style={styles.form}>
          <View style={styles.input}>
            <Text style={styles.inputLabel}>Full Name</Text>

            <TextInput
              clearButtonMode="while-editing"
              onChangeText={name => setForm({ ...form, name })}
              placeholder="John Doe"
              style={styles.inputControl}
              value={form.name} />
          </View>

          <View style={styles.input}>
            <Text style={styles.inputLabel}>Email Address</Text>

            <TextInput
              autoCapitalize="none"
              autoCorrect={false}
              clearButtonMode="while-editing"
              keyboardType="email-address"
              onChangeText={email => setForm({ ...form, email })}
              placeholder="john@example.com"
              style={styles.inputControl}
              value={form.email} />
          </View>

          <View style={styles.input}>
            <Text style={styles.inputLabel}>Password</Text>

            <TextInput
              autoCorrect={false}
              clearButtonMode="while-editing"
              onChangeText={password => setForm({ ...form, password })}
              placeholder="********"
              style={styles.inputControl}
              secureTextEntry={true}
              value={form.password} />
          </View>

          <View style={styles.input}>
            <Text style={styles.inputLabel}>Confirm Password</Text>

            <TextInput
              autoCorrect={false}
              clearButtonMode="while-editing"
              onChangeText={confirmPassword =>
                setForm({ ...form, confirmPassword })
              }
              placeholder="********"
              style={styles.inputControl}
              secureTextEntry={true}
              value={form.confirmPassword} />
          </View>

          <View style={styles.formAction}>
            <TouchableOpacity
              onPress={() => {
                // handle onPress
              }}
              style={styles.btn}>
              <Text style={styles.btnText}>Get Started</Text>
            </TouchableOpacity>
          </View>
        </KeyboardAwareScrollView>
      </View>

      <TouchableOpacity
        onPress={() => {
          // handle link
        }}>
        <Text style={styles.formFooter}>
          Already have an account?{' '}
          <Text style={{ textDecorationLine: 'underline' }}>Sign in</Text>
        </Text>
      </TouchableOpacity>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    paddingHorizontal: 24,
    paddingBottom: 16,
  },
  title: {
    fontSize: 31,
    fontWeight: '700',
    color: '#1D2A32',
    marginBottom: 6,
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
  },
  /** Header */
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 12,
  },
  headerBack: {
    padding: 8,
    paddingTop: 0,
    position: 'relative',
    marginLeft: -16,
  },
  /** Form */
  form: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    marginTop: 24,
  },
  formAction: {
    marginTop: 4,
    marginBottom: 16,
  },
  formFooter: {
    paddingVertical: 24,
    fontSize: 15,
    fontWeight: '600',
    color: '#222',
    textAlign: 'center',
    letterSpacing: 0.15,
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 50,
    backgroundColor: '#fff',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    borderWidth: 1,
    borderColor: '#C9D3DB',
    borderStyle: 'solid',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 30,
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderWidth: 1,
    backgroundColor: '#075eec',
    borderColor: '#075eec',
  },
  btnText: {
    fontSize: 18,
    lineHeight: 26,
    fontWeight: '600',
    color: '#fff',
  },
});
import React, { useState } from 'react';
import {
  StyleSheet,
  SafeAreaView,
  View,
  TouchableOpacity,
  Text,
  TextInput,
} from 'react-native';
import FeatherIcon from '@expo/vector-icons/Feather';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';

export default function Example() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    password: '',
    confirmPassword: '',
  });
  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: '#e8ecf4' }}>
      <View style={styles.container}>
        <View style={styles.header}>
          <TouchableOpacity
            onPress={() => {
              // handle onPress
            }}
            style={styles.headerBack}>
            <FeatherIcon
              color="#1D2A32"
              name="chevron-left"
              size={30} />
          </TouchableOpacity>
        </View>

        <Text style={styles.title}>Let's Get Started!</Text>

        <Text style={styles.subtitle}>
          Fill in the fields below to get started with your new account.
        </Text>

        <KeyboardAwareScrollView style={styles.form}>
          <View style={styles.input}>
            <Text style={styles.inputLabel}>Full Name</Text>

            <TextInput
              clearButtonMode="while-editing"
              onChangeText={name => setForm({ ...form, name })}
              placeholder="John Doe"
              style={styles.inputControl}
              value={form.name} />
          </View>

          <View style={styles.input}>
            <Text style={styles.inputLabel}>Email Address</Text>

            <TextInput
              autoCapitalize="none"
              autoCorrect={false}
              clearButtonMode="while-editing"
              keyboardType="email-address"
              onChangeText={email => setForm({ ...form, email })}
              placeholder="john@example.com"
              style={styles.inputControl}
              value={form.email} />
          </View>

          <View style={styles.input}>
            <Text style={styles.inputLabel}>Password</Text>

            <TextInput
              autoCorrect={false}
              clearButtonMode="while-editing"
              onChangeText={password => setForm({ ...form, password })}
              placeholder="********"
              style={styles.inputControl}
              secureTextEntry={true}
              value={form.password} />
          </View>

          <View style={styles.input}>
            <Text style={styles.inputLabel}>Confirm Password</Text>

            <TextInput
              autoCorrect={false}
              clearButtonMode="while-editing"
              onChangeText={confirmPassword =>
                setForm({ ...form, confirmPassword })
              }
              placeholder="********"
              style={styles.inputControl}
              secureTextEntry={true}
              value={form.confirmPassword} />
          </View>

          <View style={styles.formAction}>
            <TouchableOpacity
              onPress={() => {
                // handle onPress
              }}
              style={styles.btn}>
              <Text style={styles.btnText}>Get Started</Text>
            </TouchableOpacity>
          </View>
        </KeyboardAwareScrollView>
      </View>

      <TouchableOpacity
        onPress={() => {
          // handle link
        }}>
        <Text style={styles.formFooter}>
          Already have an account?{' '}
          <Text style={{ textDecorationLine: 'underline' }}>Sign in</Text>
        </Text>
      </TouchableOpacity>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    paddingHorizontal: 24,
    paddingBottom: 16,
  },
  title: {
    fontSize: 31,
    fontWeight: '700',
    color: '#1D2A32',
    marginBottom: 6,
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
  },
  /** Header */
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 12,
  },
  headerBack: {
    padding: 8,
    paddingTop: 0,
    position: 'relative',
    marginLeft: -16,
  },
  /** Form */
  form: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    marginTop: 24,
  },
  formAction: {
    marginTop: 4,
    marginBottom: 16,
  },
  formFooter: {
    paddingVertical: 24,
    fontSize: 15,
    fontWeight: '600',
    color: '#222',
    textAlign: 'center',
    letterSpacing: 0.15,
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 50,
    backgroundColor: '#fff',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    borderWidth: 1,
    borderColor: '#C9D3DB',
    borderStyle: 'solid',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 30,
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderWidth: 1,
    backgroundColor: '#075eec',
    borderColor: '#075eec',
  },
  btnText: {
    fontSize: 18,
    lineHeight: 26,
    fontWeight: '600',
    color: '#fff',
  },
});

Form Validation 📝

UI/UX implementation of the React Native Component with form validation, error messages, and asynchronous submission loading screen.

import React, { useState } from 'react';
import {
  StyleSheet,
  Alert,
  Keyboard,
  SafeAreaView,
  View,
  ActivityIndicator,
  TouchableOpacity,
  Text,
  TextInput,
} from 'react-native';
import { useForm, Controller } from 'react-hook-form';
import FeatherIcon from 'react-native-vector-icons/Feather';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';

export default function Example() {
  const [loading, setLoading] = useState(false);
  const {
    handleSubmit,
    control,
    formState: { errors },
    reset,
    watch,
  } = useForm({
    mode: 'onBlur',
    defaultValues: {
      name: '',
      email: '',
      password: '',
      confirmPassword: '',
    },
  });

  const onSubmit = async data => {
    setLoading(true);
    Keyboard.dismiss();

    // Replace this with the authentication API call.
    await new Promise(resolve => setTimeout(resolve, 2000));

    reset();
    setLoading(false);

    Alert.alert(
      'Successful submission!',
      'New account is created, navigating to the next screen! (Debug Message)',
    );
  };

  return (
    <>
      {loading && (
        <View style={styles.activityOverflow}>
          <ActivityIndicator size="large" color="#fff" />
        </View>
      )}

      <SafeAreaView style={{ flex: 1, backgroundColor: '#e8ecf4' }}>
        <View style={styles.container}>
          <View style={styles.header}>
            <TouchableOpacity
              onPress={() => {
                // handle onPress
              }}
              style={styles.headerBack}>
              <FeatherIcon
                color="#1D2A32"
                name="chevron-left"
                size={30} />
            </TouchableOpacity>
          </View>

          <Text style={styles.title}>Let's Get Started!</Text>

          <Text style={styles.subtitle}>
            Fill in the fields below to get started with your new account.
          </Text>

          <KeyboardAwareScrollView style={styles.form}>
            <View style={styles.input}>
              <Text style={styles.inputLabel}>Full Name</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      clearButtonMode="while-editing"
                      placeholder="John Doe"
                      style={styles.inputControl}
                      onBlur={onBlur}
                      onChangeText={name => onChange(name)}
                      value={value} />
                  )}
                  name="name"
                  rules={{
                    required: {
                      value: true,
                      message: 'Full Name is required.',
                    },
                  }}
                />
                {errors.name && (
                  <Text style={styles.inputError}>{errors.name.message}</Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Email Address</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCapitalize="none"
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      keyboardType="email-address"
                      placeholder="john@example.com"
                      style={styles.inputControl}
                      onBlur={onBlur}
                      onChangeText={email => onChange(email)}
                      value={value} />
                  )}
                  name="email"
                  rules={{
                    required: {
                      value: true,
                      message: 'Email Address is required.',
                    },

                    pattern: {
                      value: /\S+@\S+\.\S+/,
                      message: 'Please enter a valid email address.',
                    },
                  }}
                />
                {errors.email && (
                  <Text style={styles.inputError}>{errors.email.message}</Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Password</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      placeholder="********"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={password => onChange(password)}
                      value={value} />
                  )}
                  name="password"
                  rules={{
                    required: {
                      value: true,
                      message: 'Password is required.',
                    },

                    minLength: {
                      value: 6,
                      message: 'Password must be at least 6 characters.',
                    },
                  }}
                />
                {errors.password && (
                  <Text style={styles.inputError}>
                    {errors.password.message}
                  </Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Confirm Password</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      placeholder="********"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={confirmPassword =>
                        onChange(confirmPassword)
                      }
                      value={value} />
                  )}
                  name="confirmPassword"
                  rules={{
                    required: {
                      value: true,
                      message: 'Confirm Password is required.',
                    },

                    validate: val => {
                      if (watch('password') !== val) {
                        return 'Passwords must match.';
                      }
                    },
                  }}
                />
                {errors.confirmPassword && (
                  <Text style={styles.inputError}>
                    {errors.confirmPassword.message}
                  </Text>
                )}
              </>
            </View>

            <View style={styles.formAction}>
              <TouchableOpacity
                onPress={handleSubmit(onSubmit)}
                style={styles.btn}>
                <Text style={styles.btnText}>Get Started</Text>
              </TouchableOpacity>
            </View>
          </KeyboardAwareScrollView>
        </View>

        <TouchableOpacity
          onPress={() => {
            // handle link
          }}>
          <Text style={styles.formFooter}>
            Already have an account?{' '}
            <Text style={{ textDecorationLine: 'underline' }}>Sign in</Text>
          </Text>
        </TouchableOpacity>
      </SafeAreaView>
    </>
  );
}

const styles = StyleSheet.create({
  activityOverflow: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    right: 0,
    left: 0,
    zIndex: 9999,
    backgroundColor: 'rgba(0, 0, 0, 0.35)',
    alignItems: 'center',
    justifyContent: 'center',
    paddingBottom: 60,
  },
  container: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    paddingHorizontal: 24,
    paddingBottom: 16,
  },
  title: {
    fontSize: 31,
    fontWeight: '700',
    color: '#1D2A32',
    marginBottom: 6,
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
  },
  /** Header */
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 12,
  },
  headerBack: {
    padding: 8,
    paddingTop: 0,
    position: 'relative',
    marginLeft: -16,
  },
  /** Form */
  form: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    marginTop: 24,
  },
  formAction: {
    marginTop: 4,
    marginBottom: 16,
  },
  formFooter: {
    paddingVertical: 24,
    fontSize: 15,
    fontWeight: '600',
    color: '#222',
    textAlign: 'center',
    letterSpacing: 0.15,
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 50,
    backgroundColor: '#fff',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    borderWidth: 1,
    borderColor: '#C9D3DB',
    borderStyle: 'solid',
  },
  inputError: {
    marginTop: 4,
    fontSize: 14,
    lineHeight: 20,
    fontWeight: '500',
    color: 'red',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 30,
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderWidth: 1,
    backgroundColor: '#075eec',
    borderColor: '#075eec',
  },
  btnText: {
    fontSize: 18,
    lineHeight: 26,
    fontWeight: '600',
    color: '#fff',
  },
});
import React, { useState } from 'react';
import {
  StyleSheet,
  Alert,
  Keyboard,
  SafeAreaView,
  View,
  ActivityIndicator,
  TouchableOpacity,
  Text,
  TextInput,
} from 'react-native';
import { useForm, Controller } from 'react-hook-form';
import FeatherIcon from '@expo/vector-icons/Feather';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';

export default function Example() {
  const [loading, setLoading] = useState(false);
  const {
    handleSubmit,
    control,
    formState: { errors },
    reset,
    watch,
  } = useForm({
    mode: 'onBlur',
    defaultValues: {
      name: '',
      email: '',
      password: '',
      confirmPassword: '',
    },
  });

  const onSubmit = async data => {
    setLoading(true);
    Keyboard.dismiss();

    // Replace this with the authentication API call.
    await new Promise(resolve => setTimeout(resolve, 2000));

    reset();
    setLoading(false);

    Alert.alert(
      'Successful submission!',
      'New account is created, navigating to the next screen! (Debug Message)',
    );
  };

  return (
    <>
      {loading && (
        <View style={styles.activityOverflow}>
          <ActivityIndicator size="large" color="#fff" />
        </View>
      )}

      <SafeAreaView style={{ flex: 1, backgroundColor: '#e8ecf4' }}>
        <View style={styles.container}>
          <View style={styles.header}>
            <TouchableOpacity
              onPress={() => {
                // handle onPress
              }}
              style={styles.headerBack}>
              <FeatherIcon
                color="#1D2A32"
                name="chevron-left"
                size={30} />
            </TouchableOpacity>
          </View>

          <Text style={styles.title}>Let's Get Started!</Text>

          <Text style={styles.subtitle}>
            Fill in the fields below to get started with your new account.
          </Text>

          <KeyboardAwareScrollView style={styles.form}>
            <View style={styles.input}>
              <Text style={styles.inputLabel}>Full Name</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      clearButtonMode="while-editing"
                      placeholder="John Doe"
                      style={styles.inputControl}
                      onBlur={onBlur}
                      onChangeText={name => onChange(name)}
                      value={value} />
                  )}
                  name="name"
                  rules={{
                    required: {
                      value: true,
                      message: 'Full Name is required.',
                    },
                  }}
                />
                {errors.name && (
                  <Text style={styles.inputError}>{errors.name.message}</Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Email Address</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCapitalize="none"
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      keyboardType="email-address"
                      placeholder="john@example.com"
                      style={styles.inputControl}
                      onBlur={onBlur}
                      onChangeText={email => onChange(email)}
                      value={value} />
                  )}
                  name="email"
                  rules={{
                    required: {
                      value: true,
                      message: 'Email Address is required.',
                    },

                    pattern: {
                      value: /\S+@\S+\.\S+/,
                      message: 'Please enter a valid email address.',
                    },
                  }}
                />
                {errors.email && (
                  <Text style={styles.inputError}>{errors.email.message}</Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Password</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      placeholder="********"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={password => onChange(password)}
                      value={value} />
                  )}
                  name="password"
                  rules={{
                    required: {
                      value: true,
                      message: 'Password is required.',
                    },

                    minLength: {
                      value: 6,
                      message: 'Password must be at least 6 characters.',
                    },
                  }}
                />
                {errors.password && (
                  <Text style={styles.inputError}>
                    {errors.password.message}
                  </Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Confirm Password</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      placeholder="********"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={confirmPassword =>
                        onChange(confirmPassword)
                      }
                      value={value} />
                  )}
                  name="confirmPassword"
                  rules={{
                    required: {
                      value: true,
                      message: 'Confirm Password is required.',
                    },

                    validate: val => {
                      if (watch('password') !== val) {
                        return 'Passwords must match.';
                      }
                    },
                  }}
                />
                {errors.confirmPassword && (
                  <Text style={styles.inputError}>
                    {errors.confirmPassword.message}
                  </Text>
                )}
              </>
            </View>

            <View style={styles.formAction}>
              <TouchableOpacity
                onPress={handleSubmit(onSubmit)}
                style={styles.btn}>
                <Text style={styles.btnText}>Get Started</Text>
              </TouchableOpacity>
            </View>
          </KeyboardAwareScrollView>
        </View>

        <TouchableOpacity
          onPress={() => {
            // handle link
          }}>
          <Text style={styles.formFooter}>
            Already have an account?{' '}
            <Text style={{ textDecorationLine: 'underline' }}>Sign in</Text>
          </Text>
        </TouchableOpacity>
      </SafeAreaView>
    </>
  );
}

const styles = StyleSheet.create({
  activityOverflow: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    right: 0,
    left: 0,
    zIndex: 9999,
    backgroundColor: 'rgba(0, 0, 0, 0.35)',
    alignItems: 'center',
    justifyContent: 'center',
    paddingBottom: 60,
  },
  container: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    paddingHorizontal: 24,
    paddingBottom: 16,
  },
  title: {
    fontSize: 31,
    fontWeight: '700',
    color: '#1D2A32',
    marginBottom: 6,
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
  },
  /** Header */
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 12,
  },
  headerBack: {
    padding: 8,
    paddingTop: 0,
    position: 'relative',
    marginLeft: -16,
  },
  /** Form */
  form: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    marginTop: 24,
  },
  formAction: {
    marginTop: 4,
    marginBottom: 16,
  },
  formFooter: {
    paddingVertical: 24,
    fontSize: 15,
    fontWeight: '600',
    color: '#222',
    textAlign: 'center',
    letterSpacing: 0.15,
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 50,
    backgroundColor: '#fff',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    borderWidth: 1,
    borderColor: '#C9D3DB',
    borderStyle: 'solid',
  },
  inputError: {
    marginTop: 4,
    fontSize: 14,
    lineHeight: 20,
    fontWeight: '500',
    color: 'red',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 30,
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderWidth: 1,
    backgroundColor: '#075eec',
    borderColor: '#075eec',
  },
  btnText: {
    fontSize: 18,
    lineHeight: 26,
    fontWeight: '600',
    color: '#fff',
  },
});

User Authentication 🔒

UI/UX implementation of the React Native Component with JWT authentication, local storage integration, submission loading screen, and NodeJS (Express) server.

import React, { useState, useCallback, useEffect } from 'react';
import {
  StyleSheet,
  Alert,
  Keyboard,
  TouchableOpacity,
  SafeAreaView,
  View,
  Text,
  ActivityIndicator,
  TextInput,
} from 'react-native';
import { useForm, Controller } from 'react-hook-form';
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
import FeatherIcon from 'react-native-vector-icons/Feather';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';

export default function Example() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const {
    handleSubmit,
    control,
    formState: { errors },
    reset,
    watch,
  } = useForm({
    mode: 'onBlur',
    defaultValues: {
      email: '',
      password: '',
    },
  });

  const loadUser = useCallback(async token => {
    try {
      setLoading(true);
      const { data } = await axios.get('https://demo.withfra.me/user', {
        headers: {
          Authorization: `Bearer ${token}`,
        },
      });

      if (!data.user) {
        throw new Error('User not found.');
      }

      setUser(data.user);
    } catch (err) {
      const message =
        err.response?.data?.message ?? err.message ?? 'Something went wrong!';
      Alert.alert('Oops!', message);

      // Unauthorized request
      if (err.response?.status === 401) {
        await AsyncStorage.removeItem('token');
      }
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    // Try loading the user if token exists in local storage.
    const loadExistingUser = async () => {
      const token = await AsyncStorage.getItem('token');

      if (!token) {
        return;
      }

      await loadUser(token);
    };
    loadExistingUser();
  }, [loadUser]);

  const onSubmit = async data => {
    setLoading(true);
    Keyboard.dismiss();

    try {
      const {
        data: { token },
      } = await axios.post('https://demo.withfra.me/signup', data);

      // Save JWT token in local storage to authenticate user the next time.
      await AsyncStorage.setItem('token', token);
      await loadUser(token);

      reset();
    } catch (err) {
      const message =
        err.response?.data?.message ?? err.message ?? 'Something went wrong!';
      Alert.alert('Oops!', message);
    } finally {
      setLoading(false);
    }
  };

  if (user) {
    return (
      <View style={styles.loggedIn}>
        <Text style={styles.loggedInTitle}>Welcome, {user.name}!</Text>

        <TouchableOpacity
          onPress={async () => {
            await AsyncStorage.removeItem('token');
            setUser(null);
          }}>
          <Text style={styles.loggedInLink}>Log out</Text>
        </TouchableOpacity>
      </View>
    );
  }

  return (
    <>
      {loading && (
        <View style={styles.activityOverflow}>
          <ActivityIndicator size="large" color="#fff" />
        </View>
      )}

      <SafeAreaView style={{ flex: 1, backgroundColor: '#e8ecf4' }}>
        <View style={styles.container}>
          <View style={styles.header}>
            <TouchableOpacity
              onPress={() => {
                // handle onPress
              }}
              style={styles.headerBack}>
              <FeatherIcon
                color="#1D2A32"
                name="chevron-left"
                size={30} />
            </TouchableOpacity>
          </View>

          <Text style={styles.title}>Let's Get Started!</Text>

          <Text style={styles.subtitle}>
            Fill in the fields below to get started with your new account.
          </Text>

          <KeyboardAwareScrollView style={styles.form}>
            <View style={styles.input}>
              <Text style={styles.inputLabel}>Full Name</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      clearButtonMode="while-editing"
                      placeholder="John Doe"
                      style={styles.inputControl}
                      onBlur={onBlur}
                      onChangeText={name => onChange(name)}
                      value={value} />
                  )}
                  name="name"
                  rules={{
                    required: {
                      value: true,
                      message: 'Full Name is required.',
                    },
                  }}
                />
                {errors.name && (
                  <Text style={styles.inputError}>{errors.name.message}</Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Email Address</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCapitalize="none"
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      keyboardType="email-address"
                      placeholder="john@example.com"
                      style={styles.inputControl}
                      onBlur={onBlur}
                      onChangeText={email => onChange(email)}
                      value={value} />
                  )}
                  name="email"
                  rules={{
                    required: {
                      value: true,
                      message: 'Email Address is required.',
                    },

                    pattern: {
                      value: /\S+@\S+\.\S+/,
                      message: 'Please enter a valid email address.',
                    },
                  }}
                />
                {errors.email && (
                  <Text style={styles.inputError}>{errors.email.message}</Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Password</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      placeholder="********"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={password => onChange(password)}
                      value={value} />
                  )}
                  name="password"
                  rules={{
                    required: {
                      value: true,
                      message: 'Password is required.',
                    },

                    minLength: {
                      value: 6,
                      message: 'Password must be at least 6 characters.',
                    },
                  }}
                />
                {errors.password && (
                  <Text style={styles.inputError}>
                    {errors.password.message}
                  </Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Confirm Password</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      placeholder="********"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={confirmPassword =>
                        onChange(confirmPassword)
                      }
                      value={value} />
                  )}
                  name="confirmPassword"
                  rules={{
                    required: {
                      value: true,
                      message: 'Confirm Password is required.',
                    },

                    validate: val => {
                      if (watch('password') !== val) {
                        return 'Passwords must match.';
                      }
                    },
                  }}
                />
                {errors.confirmPassword && (
                  <Text style={styles.inputError}>
                    {errors.confirmPassword.message}
                  </Text>
                )}
              </>
            </View>

            <View style={styles.formAction}>
              <TouchableOpacity
                onPress={handleSubmit(onSubmit)}
                style={styles.btn}>
                <Text style={styles.btnText}>Get Started</Text>
              </TouchableOpacity>
            </View>
          </KeyboardAwareScrollView>
        </View>

        <TouchableOpacity
          onPress={() => {
            // handle link
          }}>
          <Text style={styles.formFooter}>
            Already have an account?{' '}
            <Text style={{ textDecorationLine: 'underline' }}>Sign in</Text>
          </Text>
        </TouchableOpacity>
      </SafeAreaView>
    </>
  );
}

const styles = StyleSheet.create({
  activityOverflow: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    right: 0,
    left: 0,
    zIndex: 9999,
    backgroundColor: 'rgba(0, 0, 0, 0.35)',
    alignItems: 'center',
    justifyContent: 'center',
    paddingBottom: 60,
  },
  container: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    paddingHorizontal: 24,
    paddingBottom: 16,
  },
  title: {
    fontSize: 31,
    fontWeight: '700',
    color: '#1D2A32',
    marginBottom: 6,
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
  },
  loggedIn: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loggedInTitle: {
    fontSize: 17,
    fontWeight: '500',
    color: '#1d1d1d',
    marginBottom: 6,
  },
  loggedInLink: {
    fontSize: 15,
    color: '#007aff',
    textDecorationLine: 'underline',
  },
  /** Header */
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 12,
  },
  headerBack: {
    padding: 8,
    paddingTop: 0,
    position: 'relative',
    marginLeft: -16,
  },
  /** Form */
  form: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    marginTop: 24,
  },
  formAction: {
    marginTop: 4,
    marginBottom: 16,
  },
  formFooter: {
    paddingVertical: 24,
    fontSize: 15,
    fontWeight: '600',
    color: '#222',
    textAlign: 'center',
    letterSpacing: 0.15,
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 50,
    backgroundColor: '#fff',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    borderWidth: 1,
    borderColor: '#C9D3DB',
    borderStyle: 'solid',
  },
  inputError: {
    marginTop: 4,
    fontSize: 14,
    lineHeight: 20,
    fontWeight: '500',
    color: 'red',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 30,
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderWidth: 1,
    backgroundColor: '#075eec',
    borderColor: '#075eec',
  },
  btnText: {
    fontSize: 18,
    lineHeight: 26,
    fontWeight: '600',
    color: '#fff',
  },
});
import React, { useState, useCallback, useEffect } from 'react';
import {
  StyleSheet,
  Alert,
  Keyboard,
  TouchableOpacity,
  SafeAreaView,
  View,
  Text,
  ActivityIndicator,
  TextInput,
} from 'react-native';
import { useForm, Controller } from 'react-hook-form';
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
import FeatherIcon from '@expo/vector-icons/Feather';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';

export default function Example() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const {
    handleSubmit,
    control,
    formState: { errors },
    reset,
    watch,
  } = useForm({
    mode: 'onBlur',
    defaultValues: {
      email: '',
      password: '',
    },
  });

  const loadUser = useCallback(async token => {
    try {
      setLoading(true);
      const { data } = await axios.get('https://demo.withfra.me/user', {
        headers: {
          Authorization: `Bearer ${token}`,
        },
      });

      if (!data.user) {
        throw new Error('User not found.');
      }

      setUser(data.user);
    } catch (err) {
      const message =
        err.response?.data?.message ?? err.message ?? 'Something went wrong!';
      Alert.alert('Oops!', message);

      // Unauthorized request
      if (err.response?.status === 401) {
        await AsyncStorage.removeItem('token');
      }
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    // Try loading the user if token exists in local storage.
    const loadExistingUser = async () => {
      const token = await AsyncStorage.getItem('token');

      if (!token) {
        return;
      }

      await loadUser(token);
    };
    loadExistingUser();
  }, [loadUser]);

  const onSubmit = async data => {
    setLoading(true);
    Keyboard.dismiss();

    try {
      const {
        data: { token },
      } = await axios.post('https://demo.withfra.me/signup', data);

      // Save JWT token in local storage to authenticate user the next time.
      await AsyncStorage.setItem('token', token);
      await loadUser(token);

      reset();
    } catch (err) {
      const message =
        err.response?.data?.message ?? err.message ?? 'Something went wrong!';
      Alert.alert('Oops!', message);
    } finally {
      setLoading(false);
    }
  };

  if (user) {
    return (
      <View style={styles.loggedIn}>
        <Text style={styles.loggedInTitle}>Welcome, {user.name}!</Text>

        <TouchableOpacity
          onPress={async () => {
            await AsyncStorage.removeItem('token');
            setUser(null);
          }}>
          <Text style={styles.loggedInLink}>Log out</Text>
        </TouchableOpacity>
      </View>
    );
  }

  return (
    <>
      {loading && (
        <View style={styles.activityOverflow}>
          <ActivityIndicator size="large" color="#fff" />
        </View>
      )}

      <SafeAreaView style={{ flex: 1, backgroundColor: '#e8ecf4' }}>
        <View style={styles.container}>
          <View style={styles.header}>
            <TouchableOpacity
              onPress={() => {
                // handle onPress
              }}
              style={styles.headerBack}>
              <FeatherIcon
                color="#1D2A32"
                name="chevron-left"
                size={30} />
            </TouchableOpacity>
          </View>

          <Text style={styles.title}>Let's Get Started!</Text>

          <Text style={styles.subtitle}>
            Fill in the fields below to get started with your new account.
          </Text>

          <KeyboardAwareScrollView style={styles.form}>
            <View style={styles.input}>
              <Text style={styles.inputLabel}>Full Name</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      clearButtonMode="while-editing"
                      placeholder="John Doe"
                      style={styles.inputControl}
                      onBlur={onBlur}
                      onChangeText={name => onChange(name)}
                      value={value} />
                  )}
                  name="name"
                  rules={{
                    required: {
                      value: true,
                      message: 'Full Name is required.',
                    },
                  }}
                />
                {errors.name && (
                  <Text style={styles.inputError}>{errors.name.message}</Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Email Address</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCapitalize="none"
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      keyboardType="email-address"
                      placeholder="john@example.com"
                      style={styles.inputControl}
                      onBlur={onBlur}
                      onChangeText={email => onChange(email)}
                      value={value} />
                  )}
                  name="email"
                  rules={{
                    required: {
                      value: true,
                      message: 'Email Address is required.',
                    },

                    pattern: {
                      value: /\S+@\S+\.\S+/,
                      message: 'Please enter a valid email address.',
                    },
                  }}
                />
                {errors.email && (
                  <Text style={styles.inputError}>{errors.email.message}</Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Password</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      placeholder="********"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={password => onChange(password)}
                      value={value} />
                  )}
                  name="password"
                  rules={{
                    required: {
                      value: true,
                      message: 'Password is required.',
                    },

                    minLength: {
                      value: 6,
                      message: 'Password must be at least 6 characters.',
                    },
                  }}
                />
                {errors.password && (
                  <Text style={styles.inputError}>
                    {errors.password.message}
                  </Text>
                )}
              </>
            </View>

            <View style={styles.input}>
              <Text style={styles.inputLabel}>Confirm Password</Text>

              <>
                <Controller
                  control={control}
                  render={({ field: { onChange, onBlur, value } }) => (
                    <TextInput
                      autoCorrect={false}
                      clearButtonMode="while-editing"
                      placeholder="********"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={confirmPassword =>
                        onChange(confirmPassword)
                      }
                      value={value} />
                  )}
                  name="confirmPassword"
                  rules={{
                    required: {
                      value: true,
                      message: 'Confirm Password is required.',
                    },

                    validate: val => {
                      if (watch('password') !== val) {
                        return 'Passwords must match.';
                      }
                    },
                  }}
                />
                {errors.confirmPassword && (
                  <Text style={styles.inputError}>
                    {errors.confirmPassword.message}
                  </Text>
                )}
              </>
            </View>

            <View style={styles.formAction}>
              <TouchableOpacity
                onPress={handleSubmit(onSubmit)}
                style={styles.btn}>
                <Text style={styles.btnText}>Get Started</Text>
              </TouchableOpacity>
            </View>
          </KeyboardAwareScrollView>
        </View>

        <TouchableOpacity
          onPress={() => {
            // handle link
          }}>
          <Text style={styles.formFooter}>
            Already have an account?{' '}
            <Text style={{ textDecorationLine: 'underline' }}>Sign in</Text>
          </Text>
        </TouchableOpacity>
      </SafeAreaView>
    </>
  );
}

const styles = StyleSheet.create({
  activityOverflow: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    right: 0,
    left: 0,
    zIndex: 9999,
    backgroundColor: 'rgba(0, 0, 0, 0.35)',
    alignItems: 'center',
    justifyContent: 'center',
    paddingBottom: 60,
  },
  container: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    paddingHorizontal: 24,
    paddingBottom: 16,
  },
  title: {
    fontSize: 31,
    fontWeight: '700',
    color: '#1D2A32',
    marginBottom: 6,
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
  },
  loggedIn: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loggedInTitle: {
    fontSize: 17,
    fontWeight: '500',
    color: '#1d1d1d',
    marginBottom: 6,
  },
  loggedInLink: {
    fontSize: 15,
    color: '#007aff',
    textDecorationLine: 'underline',
  },
  /** Header */
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: 12,
  },
  headerBack: {
    padding: 8,
    paddingTop: 0,
    position: 'relative',
    marginLeft: -16,
  },
  /** Form */
  form: {
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
    marginTop: 24,
  },
  formAction: {
    marginTop: 4,
    marginBottom: 16,
  },
  formFooter: {
    paddingVertical: 24,
    fontSize: 15,
    fontWeight: '600',
    color: '#222',
    textAlign: 'center',
    letterSpacing: 0.15,
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 50,
    backgroundColor: '#fff',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    borderWidth: 1,
    borderColor: '#C9D3DB',
    borderStyle: 'solid',
  },
  inputError: {
    marginTop: 4,
    fontSize: 14,
    lineHeight: 20,
    fontWeight: '500',
    color: 'red',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 30,
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderWidth: 1,
    backgroundColor: '#075eec',
    borderColor: '#075eec',
  },
  btnText: {
    fontSize: 18,
    lineHeight: 26,
    fontWeight: '600',
    color: '#fff',
  },
});
const express = require('express');
const crypto = require('crypto');
const jwt = require('jsonwebtoken');

const app = express();
app.use(express.json());

// helper functions
const generateSaltAndHashForPassword = (password) => {
  const salt = crypto.randomBytes(16).toString('hex')
  const hash = crypto
    .pbkdf2Sync(password, salt, 1000, 64, 'sha512')
    .toString('hex')

  return {salt, hash}
}

const comparePassword = async (password, salt, hash) => {
  const inputHash = crypto
    .pbkdf2Sync(password, salt, 1000, 64, 'sha512')
    .toString('hex')
  return hash === inputHash
}

// Private properties like `salt` and `hash` should not be returned to the client.
const sanitizeUser = (_user) => {
  const user = {..._user};
  delete user.salt;
  delete user.hash;
  return user;
}

// This array represents your database.
const USERS = [
  {
    id: 0,
    name: 'John Doe',
    email: 'john@example.com',

    /**
     * Passwords should never be stored in plain text.
     * Instead, we're generating salt and hash for a given password and storing them on the user entity.
     */
    ...generateSaltAndHashForPassword('123456')
  }
]

// Replace this with your own JWT secret key.
const JWT_SECRET = 'YOUR_RANDOM_KEY'

app.get('/user', async (req, res) => {
  const header = req.get('Authorization');

  if (!header) {
    return res.status(401).json({ success: false, message: 'You are not authorized.' });
  }

  const token = header.split(' ')[1]; // Extract token from `Bearer JWT_TOKEN` header.

  await new Promise(resolve => setTimeout(resolve, 1000))

  try {
    const payload = jwt.verify(token, JWT_SECRET);
    const user = USERS.find(user => user.id === payload.id);

    if (!user) {
      throw new Error('Invalid user.');
    }

    return res.json({ success: true, user: sanitizeUser(user) });
  } catch (err) {
    return res.status(401).json({ success: false, message: 'Invalid JWT token.' });
  }
})

app.post('/login', async (req, res) => {
  const {email, password} = req.body;

  const user = USERS.find(user => user.email === email);

  if (!user) {
    return res.status(400).json({ success: false, message: 'Could not find user with this email address, please try again.' });
  }

  // Generate hash for entered password and compare it to the hash from the database.
  if (!await comparePassword(password, user.salt, user.hash)) {
    return res.status(400).json({ success: false, message: 'Unable to log in with provided credentials.' });
  }

  // Create JWT token once user is authenticated.
  const payload = {
    id: user.id,
    name: user.name,
    email: user.email
  }
  const token = jwt.sign(payload, JWT_SECRET, {
    expiresIn: '7d',
  });

  return res.status(200).json({ success: true, token })
})

app.post('/signup', async (req, res) => {
  const {name, email, password} = req.body;

  const existingUser = USERS.find(user => user.email === email);

  if (existingUser) {
    return res.status(400).json({ success: false, message: 'User with this email already exists.' });
  }

  const user = {
    id: USERS.length,
    name: name ?? 'John Doe',
    email,
    ...generateSaltAndHashForPassword(password)
  }

  // Save user entity in the database
  USERS.push(user);

  // Create JWT token once user is authenticated.
  const payload = {
    id: user.id,
    name: user.name,
    email: user.email
  }
  const token = jwt.sign(payload, JWT_SECRET, {
    expiresIn: '7d',
  });

  return res.status(200).json({ success: true, token })
})


app.listen(4242, () => {
  console.log('Express server is running on port 4242.');
})

Dependencies

Before getting started, make sure you have all the necessary dependencies for this component. Follow the steps below to install any missing dependencies.

Explore more components

Over 100 Ready to Use React Native Components for all your needs.

Never miss a beat

Stay in the loop with our latest updates and offers - no spam, just the good stuff!