Simple Sign In Form

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

import React, { useState } from 'react';
import {
  StyleSheet,
  SafeAreaView,
  View,
  Text,
  TouchableOpacity,
  TextInput,
} from 'react-native';

export default function Example() {
  const [form, setForm] = useState({
    email: '',
    password: '',
  });
  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
      <View style={styles.container}>
        <View style={styles.header}>
          <Text style={styles.title}>Welcome back!</Text>

          <Text style={styles.subtitle}>Sign in to your account</Text>
        </View>

        <View style={styles.form}>
          <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"
              placeholderTextColor="#6b7280"
              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="********"
              placeholderTextColor="#6b7280"
              style={styles.inputControl}
              secureTextEntry={true}
              value={form.password} />
          </View>

          <View style={styles.formAction}>
            <TouchableOpacity
              onPress={() => {
                // handle onPress
              }}>
              <View style={styles.btn}>
                <Text style={styles.btnText}>Sign in</Text>
              </View>
            </TouchableOpacity>
          </View>

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

const styles = StyleSheet.create({
  container: {
    padding: 24,
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
  },
  header: {
    marginVertical: 36,
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#1d1d1d',
    marginBottom: 6,
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
    textAlign: 'center',
  },
  /** Form */
  form: {
    marginBottom: 24,
  },
  formAction: {
    marginVertical: 24,
  },
  formFooter: {
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    textAlign: 'center',
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 44,
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 8,
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderWidth: 1,
    backgroundColor: '#007aff',
    borderColor: '#007aff',
  },
  btnText: {
    fontSize: 17,
    lineHeight: 24,
    fontWeight: '600',
    color: '#fff',
  },
});
import React, { useState } from 'react';
import {
  StyleSheet,
  SafeAreaView,
  View,
  Text,
  TouchableOpacity,
  TextInput,
} from 'react-native';

export default function Example() {
  const [form, setForm] = useState({
    email: '',
    password: '',
  });
  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
      <View style={styles.container}>
        <View style={styles.header}>
          <Text style={styles.title}>Welcome back!</Text>

          <Text style={styles.subtitle}>Sign in to your account</Text>
        </View>

        <View style={styles.form}>
          <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"
              placeholderTextColor="#6b7280"
              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="********"
              placeholderTextColor="#6b7280"
              style={styles.inputControl}
              secureTextEntry={true}
              value={form.password} />
          </View>

          <View style={styles.formAction}>
            <TouchableOpacity
              onPress={() => {
                // handle onPress
              }}>
              <View style={styles.btn}>
                <Text style={styles.btnText}>Sign in</Text>
              </View>
            </TouchableOpacity>
          </View>

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

const styles = StyleSheet.create({
  container: {
    padding: 24,
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
  },
  header: {
    marginVertical: 36,
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#1d1d1d',
    marginBottom: 6,
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
    textAlign: 'center',
  },
  /** Form */
  form: {
    marginBottom: 24,
  },
  formAction: {
    marginVertical: 24,
  },
  formFooter: {
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    textAlign: 'center',
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 44,
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 8,
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderWidth: 1,
    backgroundColor: '#007aff',
    borderColor: '#007aff',
  },
  btnText: {
    fontSize: 17,
    lineHeight: 24,
    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,
  Text,
  TouchableOpacity,
  TextInput,
} from 'react-native';
import { useForm, Controller } from 'react-hook-form';

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

  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!',
      'You\'re logged in, navigating to the next screen! (Debug Message)',
    );
  };

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

      <SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
        <View style={styles.container}>
          <View style={styles.header}>
            <Text style={styles.title}>Welcome back!</Text>

            <Text style={styles.subtitle}>Sign in to your account</Text>
          </View>

          <View style={styles.form}>
            <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"
                      placeholderTextColor="#6b7280"
                      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="********"
                      placeholderTextColor="#6b7280"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={password => onChange(password)}
                      value={value} />
                  )}
                  name="password"
                  rules={{
                    required: {
                      value: true,
                      message: 'Password is required.',
                    },
                  }}
                />
                {errors.password && (
                  <Text style={styles.inputError}>
                    {errors.password.message}
                  </Text>
                )}
              </>
            </View>

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

            <TouchableOpacity
              onPress={() => {
                // handle link
              }}>
              <Text style={styles.formFooter}>
                Don't have an account?{' '}
                <Text style={{ textDecorationLine: 'underline' }}>Sign up</Text>
              </Text>
            </TouchableOpacity>
          </View>
        </View>
      </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: {
    padding: 24,
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
  },
  header: {
    marginVertical: 36,
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#1d1d1d',
    marginBottom: 6,
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
    textAlign: 'center',
  },
  /** Form */
  form: {
    marginBottom: 24,
  },
  formAction: {
    marginVertical: 24,
  },
  formFooter: {
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    textAlign: 'center',
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 44,
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
  },
  inputError: {
    marginTop: 4,
    fontSize: 14,
    lineHeight: 20,
    fontWeight: '500',
    color: 'red',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 8,
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderWidth: 1,
    backgroundColor: '#007aff',
    borderColor: '#007aff',
  },
  btnText: {
    fontSize: 17,
    lineHeight: 24,
    fontWeight: '600',
    color: '#fff',
  },
});
import React, { useState } from 'react';
import {
  StyleSheet,
  Alert,
  Keyboard,
  SafeAreaView,
  View,
  ActivityIndicator,
  Text,
  TouchableOpacity,
  TextInput,
} from 'react-native';
import { useForm, Controller } from 'react-hook-form';

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

  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!',
      'You\'re logged in, navigating to the next screen! (Debug Message)',
    );
  };

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

      <SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
        <View style={styles.container}>
          <View style={styles.header}>
            <Text style={styles.title}>Welcome back!</Text>

            <Text style={styles.subtitle}>Sign in to your account</Text>
          </View>

          <View style={styles.form}>
            <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"
                      placeholderTextColor="#6b7280"
                      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="********"
                      placeholderTextColor="#6b7280"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={password => onChange(password)}
                      value={value} />
                  )}
                  name="password"
                  rules={{
                    required: {
                      value: true,
                      message: 'Password is required.',
                    },
                  }}
                />
                {errors.password && (
                  <Text style={styles.inputError}>
                    {errors.password.message}
                  </Text>
                )}
              </>
            </View>

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

            <TouchableOpacity
              onPress={() => {
                // handle link
              }}>
              <Text style={styles.formFooter}>
                Don't have an account?{' '}
                <Text style={{ textDecorationLine: 'underline' }}>Sign up</Text>
              </Text>
            </TouchableOpacity>
          </View>
        </View>
      </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: {
    padding: 24,
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
  },
  header: {
    marginVertical: 36,
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#1d1d1d',
    marginBottom: 6,
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
    textAlign: 'center',
  },
  /** Form */
  form: {
    marginBottom: 24,
  },
  formAction: {
    marginVertical: 24,
  },
  formFooter: {
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    textAlign: 'center',
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 44,
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
  },
  inputError: {
    marginTop: 4,
    fontSize: 14,
    lineHeight: 20,
    fontWeight: '500',
    color: 'red',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 8,
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderWidth: 1,
    backgroundColor: '#007aff',
    borderColor: '#007aff',
  },
  btnText: {
    fontSize: 17,
    lineHeight: 24,
    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, useEffect, useCallback } from 'react';
import {
  StyleSheet,
  Alert,
  Keyboard,
  TouchableOpacity,
  View,
  Text,
  SafeAreaView,
  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';

export default function Example() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const {
    handleSubmit,
    control,
    formState: { errors },
    reset,
  } = 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);
    } 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/login', 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: '#fff' }}>
        <View style={styles.container}>
          <View style={styles.header}>
            <Text style={styles.title}>Welcome back!</Text>

            <Text style={styles.subtitle}>Sign in to your account</Text>
          </View>

          <View style={styles.form}>
            <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"
                      placeholderTextColor="#6b7280"
                      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="********"
                      placeholderTextColor="#6b7280"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={password => onChange(password)}
                      value={value} />
                  )}
                  name="password"
                  rules={{
                    required: {
                      value: true,
                      message: 'Password is required.',
                    },
                  }}
                />
                {errors.password && (
                  <Text style={styles.inputError}>
                    {errors.password.message}
                  </Text>
                )}
              </>
            </View>

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

            <TouchableOpacity
              onPress={() => {
                // handle link
              }}>
              <Text style={styles.formFooter}>
                Don't have an account?{' '}
                <Text style={{ textDecorationLine: 'underline' }}>Sign up</Text>
              </Text>
            </TouchableOpacity>
          </View>
        </View>
      </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: {
    padding: 24,
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
  },
  header: {
    marginVertical: 36,
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#1d1d1d',
    marginBottom: 6,
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
    textAlign: 'center',
  },
  loggedIn: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  loggedInTitle: {
    fontSize: 17,
    fontWeight: '500',
    color: '#1d1d1d',
    marginBottom: 6,
  },
  loggedInLink: {
    fontSize: 15,
    color: '#007aff',
    textDecorationLine: 'underline',
  },
  /** Form */
  form: {
    marginBottom: 24,
  },
  formAction: {
    marginVertical: 24,
  },
  formFooter: {
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    textAlign: 'center',
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 44,
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
  },
  inputError: {
    marginTop: 4,
    fontSize: 14,
    lineHeight: 20,
    fontWeight: '500',
    color: 'red',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 8,
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderWidth: 1,
    backgroundColor: '#007aff',
    borderColor: '#007aff',
  },
  btnText: {
    fontSize: 17,
    lineHeight: 24,
    fontWeight: '600',
    color: '#fff',
  },
});
import React, { useState, useEffect, useCallback } from 'react';
import {
  StyleSheet,
  Alert,
  Keyboard,
  TouchableOpacity,
  View,
  Text,
  SafeAreaView,
  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';

export default function Example() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const {
    handleSubmit,
    control,
    formState: { errors },
    reset,
  } = 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);
    } 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/login', 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: '#fff' }}>
        <View style={styles.container}>
          <View style={styles.header}>
            <Text style={styles.title}>Welcome back!</Text>

            <Text style={styles.subtitle}>Sign in to your account</Text>
          </View>

          <View style={styles.form}>
            <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"
                      placeholderTextColor="#6b7280"
                      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="********"
                      placeholderTextColor="#6b7280"
                      style={styles.inputControl}
                      secureTextEntry={true}
                      onBlur={onBlur}
                      onChangeText={password => onChange(password)}
                      value={value} />
                  )}
                  name="password"
                  rules={{
                    required: {
                      value: true,
                      message: 'Password is required.',
                    },
                  }}
                />
                {errors.password && (
                  <Text style={styles.inputError}>
                    {errors.password.message}
                  </Text>
                )}
              </>
            </View>

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

            <TouchableOpacity
              onPress={() => {
                // handle link
              }}>
              <Text style={styles.formFooter}>
                Don't have an account?{' '}
                <Text style={{ textDecorationLine: 'underline' }}>Sign up</Text>
              </Text>
            </TouchableOpacity>
          </View>
        </View>
      </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: {
    padding: 24,
    flexGrow: 1,
    flexShrink: 1,
    flexBasis: 0,
  },
  header: {
    marginVertical: 36,
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#1d1d1d',
    marginBottom: 6,
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 15,
    fontWeight: '500',
    color: '#929292',
    textAlign: 'center',
  },
  loggedIn: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  loggedInTitle: {
    fontSize: 17,
    fontWeight: '500',
    color: '#1d1d1d',
    marginBottom: 6,
  },
  loggedInLink: {
    fontSize: 15,
    color: '#007aff',
    textDecorationLine: 'underline',
  },
  /** Form */
  form: {
    marginBottom: 24,
  },
  formAction: {
    marginVertical: 24,
  },
  formFooter: {
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
    textAlign: 'center',
  },
  /** Input */
  input: {
    marginBottom: 16,
  },
  inputLabel: {
    fontSize: 17,
    fontWeight: '600',
    color: '#222',
    marginBottom: 8,
  },
  inputControl: {
    height: 44,
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 16,
    borderRadius: 12,
    fontSize: 15,
    fontWeight: '500',
    color: '#222',
  },
  inputError: {
    marginTop: 4,
    fontSize: 14,
    lineHeight: 20,
    fontWeight: '500',
    color: 'red',
  },
  /** Button */
  btn: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: 8,
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderWidth: 1,
    backgroundColor: '#007aff',
    borderColor: '#007aff',
  },
  btnText: {
    fontSize: 17,
    lineHeight: 24,
    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!