In this article you are going to learn how to create a login page using React Native. We will use latest packages of October 2020 so that all the dependencies should be up to date with the React native version > 0.63.

We will also animate the page on button clicks and on focus. This is the final application we will make –

These are the steps we are going to follow –

  1. Creating a new react application using react-native init.
  2. Create component for login page.
  3. Adding form with email and password fields.
  4. Make it interactive with Animations.

Let’s get started.

Creating a new react application using react-native init

To create a react native app, you need to have node installed along with Android sdk and cocoapods. You may learn more about installing the pre-requisites from here. After setting up the environment, we need to run the below command to create an app –

npx react-native init LoginPage

This command will setup all the required directories and code files for the development of Android and iOS apps. Next, let’s enter into this newly created project.

cd LoginPage

Create component for login page

In the src directory, create a JavaScript file with name Login.js. This is our component where we are going to add the form. The code will be –

import * as React from 'react';
import { Text, View } from 'react-native';

export default function Login() {
   return(
      <View>
         <Text>Login</Text>
      </View>
   );
}

Adding Form with Email and Password Fields

Learn creating forms using react-hook-form

Since we have created our Login component, now its time to add form in it. Our form will have Login heading, one input field for email address, one input field for password and a form submit button. We will also add icons to denote that input fields belong to email and password. Check out this code –

import * as React from 'react';
import { Text, View, TextInput, TouchableNativeFeedback } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';

export default function Login() {
  const [email, setEmail] = React.useState('');
  const [pass, setPass] = React.useState('');

  return (
    <View>
      <Text>Login</Text>
      <View>
        <Icon name="email" size={30} />
        <TextInput 
          onChangeText={text => setEmail(text)} 
          value={email} 
          placeholder={'Email Address'}
        />
      </View>
      <View>
        <Icon name="lock" size={30} />
        <TextInput 
          onChangeText={text => setPass(text)} 
          value={pass} 
          placeholder={'Password'}
        />
      </View>
      <View>
        <TouchableNativeFeedback>
          <View>
            <Text>Login</Text>
          </View>
        </TouchableNativeFeedback>
      </View>
    </View>
  );
}

Let’s understand this code. We started by importing React, Text, View, TextInput, TouchableNativeFeedback from react-native package. Also, we imported Icon from MaterialCommunityIcons file of react-native-vector-icons.

import * as React from 'react';
import { Text, View, TextInput, TouchableNativeFeedback } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';

We are setting up two state variables, one for email and another for password. These constants will hold the input values.

const [email, setEmail] = React.useState('');
const [pass, setPass] = React.useState('');

Next, we are rendering Login text, email icon, email input field, lock icon, password field and Login button. This will create the structure of our page. It still needs styling and enhancements like password fields are not obscured yet, which we will cover next.

Enhancing Usability of Input Fields

Generally, input fields are modified to represent the kind of data they can hold. For example, if an input field is meant to hold email address then we are sure that user will type ‘@’ and it might be possible that they wish to autofill it. Similarly for passwords it is mandatory to obscure the field to keep it secret and safe.

Currently our both input fields are similar and they do not qualify for their specific purpose. They are general text inputs. Let’s enhance and add features to them.

For email field, we are going to add these properties –

  1. Set textContentType – We will set it to “emailAddress” so that system will know that the particular field is of type email.
  2. autoCompleteType – We set it to “email“. This will help the user to auto fill the fields with their stored credentials.
  3. clearButtonMode– This is iOS specific. It adds a cross (x) mark at the end of the field to let you erase it with one click.
  4. keyboardType – For our field it will be “email-address“. This helps the system to open the keyboard with ‘@’ button on the front so that it is easily accessible to user. Because user is definitely going to need it.
  5. returnKeyLabel – It is defined for Android only. React Native suggests using it. It indicates the kind of return key or enter key we wish to display in keyboard. For example here we will have a password field after it, so we can have the return key in our keyboard which indicate that there is another field which we need to fill, i.e. you need to move next. So, here we will use ‘next‘ as value.
  6. returnKeyType – This is similar to returnKeyLabel but it can be used cross-platform. We set it to ‘next‘ also.

After adding all these properties, our code for email field will look like this –

<TextInput 
          textContentType={'emailAddress'} 
          onChangeText={text => setEmail(text)} 
          value={email} 
          autoCompleteType={'email'}
          clearButtonMode={'while-editing'}
          keyboardType={'email-address'}
          returnKeyLabel={'next'}
          returnKeyType={'next'}
          underlineColorAndroid={'#FF0000'}
          placeholder={'Email Address'}
/>

Similarly, our password field will have these properties but with different values. It will have one extra parameter of secureTextEntry which will obscure the text of this field. Check out the code –

<TextInput 
          textContentType={'password'} 
          onChangeText={text => setPass(text)} 
          value={pass} 
          autoCompleteType={'password'}
          clearButtonMode={'while-editing'}
          returnKeyLabel={'done'}
          returnKeyType={'done'}
          underlineColorAndroid={'#FF0000'}
          placeholder={'Password'}
          secureTextEntry={true}
/>

Our app now look like this –

Styling the Login Page

These are the few things we need to do in order to make it more attractive –

  1. Adding a background color and padding to the page.
  2. Login heading should be in center and bigger. It should have some decent color.
  3. Keeping icons and input of the text field in the same line.
  4. Styling the submit button.

Adding a background color and padding to the page

To style the page, I am going to give a dark blue background and padding of 20 from all sides. Also I will align the children to the center of the page. The code will look like this –

<View
      style={{
        padding: 20,
        backgroundColor: '#133E7C',
        flex: 1,
        justifyContent: 'center',
      }}>

Styling the Login Heading

The heading should be center aligned and bold. I will make it bigger in size for clear visibility. See the code –

<Text style={{
        alignSelf: 'center',
        fontSize: 30,
        marginBottom: 50,
        color: '#22A0B6',
        fontWeight: '600',
}}>Login</Text>

Styling icons and input fields

First of all the icon and input field needs to be in a single row. So, code for enclosing view will be like this –

<View style={{
        flexDirection: 'row',
        marginBottom: 20,
      }}>

Now since the icon and input field will position themselves in a row, we move ahead with styling them. Here is the code –

<Icon name="email" size={30} color={'#9BE6DE'} />
<TextInput
          textContentType={'emailAddress'}
          onChangeText={(text) => setEmail(text)}
          value={email}
          autoCompleteType={'email'}
          clearButtonMode={'while-editing'}
          keyboardType={'email-address'}
          returnKeyLabel={'next'}
          returnKeyType={'next'}
          placeholder={'Email Address'}
          style={{
            flex: 1,
            paddingLeft: 20,
            borderBottomColor: '#9BE6DE',
            borderBottomWidth: 1,
          }}
        />

Styling Submit Button

Here we are replacing the TouchableNativeFeedback component with TouchableHighlight so that it will become compatible with both Android and iOS. The button will be centered and elevated to give it a nice shadow. Check out the code –

<View style={{
        alignSelf: 'center',
        marginTop: 30,
      }}>
        <TouchableHighlight
         activeOpacity={0.8}
         underlayColor="#ffffff"
        >
          <View style={{
            paddingHorizontal: 50,
            paddingVertical: 10,
            borderRadius: 8,
            elevation: 8,
            shadowColor: '#0047BB',
            shadowRadius: 8,
            backgroundColor: '#0D2534',
          }}>
            <Text style={{
              color: '#9BE6DE',
            }}>Login</Text>
          </View>
        </TouchableHighlight>
      </View>

Now everything is styled and looking pretty. Check the demo in action.

Making Login Page Interactive with React Native Animation

First of all we need to decide what kind of animation we want on the screen. Like as I am thinking to change the background color when the button is pressed. Also, we can convert the button into circular progress bar with lock icon in the middle. Let’s the game begins.

Oh! one more thing, if you don’t know how animation works in react native then please refer to this guide first and come back to continue making your login page. There is also a library, React-Spring which you can use for it.

We will start by creating an animation variable and initializing it with a value of 0.

const animationVariable = React.useRef(new Animated.Value(0)).current;

Now we will create a function to run animation on this variable and change its value from 0 to 1.

const runAnimation = (toVal) => {
   Animated.spring(animationVariable, {
      toValue : toVal,
      useNativeDriver: false,
   }).start();
}

We have used spring timing function of Animated component. It provides an inbuilt effect without any need of easing.

It’s all set up. Time to make changes in background color of the page but let’s convert our View to Animated.View, otherwise animations won’t run.

<Animated.View
      style={{
        padding: 20,
        backgroundColor: animationVariable.interpolate({
                            inputRange: [0, 1],
                            outputRange: ['#133E7C', '#0D2534'],
                            extrapolate: 'clamp',
                         }),
        flex: 1,
        justifyContent: 'center',
      }}>

Till here, our application will look like this –

Time to animate the button. For this, I have these plans –

  1. Change shape to circle and update background color.
  2. Change ‘Login’ text to lock icon.
  3. Animate a ripple from center.

Change button shape to circle and update background color

This process starts by converting the View into Animated.View. We are also going to change the structure a bit. This is our current button code –

<TouchableHighlight
         activeOpacity={0.8}
         underlayColor="#ffffff"
        >
          <View style={{
            paddingHorizontal: 50,
            paddingVertical: 10,
            borderRadius: 8,
            elevation: 8,
            shadowColor: '#0047BB',
            shadowRadius: 8,
            backgroundColor: '#0D2534',
          }}>
            <Text style={{
              color: '#9BE6DE',
            }}>Login</Text>
          </View>
        </TouchableHighlight>

We will remove padding, and use width/height. This is because animation works better with width/height than paddings. Although you can use paddings if you wish but as my preference, I will go with width/height. Also, pay attention that we set the value of useNativeDriver as false (check here). This is the reason. Native drivers works with few styles only like transform and opacity (Read more).

Lets update the code according to our discussion –

<Animated.View style={{
            width: 120,
            height: 40,
            alignItems: 'center',
            justifyContent: 'center',
            borderRadius: 8,
            elevation: 8,
            shadowColor: '#0047BB',
            shadowRadius: 8,
            backgroundColor: '#0D2534',
          }}>

We added alignItems and justifyContent to be center so that Login text and lock icon (will add next) could be in center to the button both horizontally as well as vertically.

Now I will interpolate the width, height, background and border radius of the button so that it can become circular and change color with animation variable.

Our circular button transformation with have radius of 30. This means the height and width will be 60 and border radius could be anything >30. The code will become this –

<Animated.View style={{
            width: animationVariable.interpolate({
                            inputRange: [0, 1],
                            outputRange: [120, 60],
                            extrapolate: 'clamp',
                          }),
            height: animationVariable.interpolate({
                            inputRange: [0, 1],
                            outputRange: [40, 60],
                            extrapolate: 'clamp',
                          }),
            borderRadius: animationVariable.interpolate({
                            inputRange: [0, 1],
                            outputRange: [8, 60],
                            extrapolate: 'clamp',
                          }),
            elevation: 8,
            shadowColor: '#0047BB',
            shadowRadius: 8,
            backgroundColor: animationVariable.interpolate({
                            inputRange: [0, 1],
                            outputRange: ['#0D2534', '#652EC7'],
                            extrapolate: 'clamp',
                          }),
            alignItems: 'center',
            justifyContent: 'center',
          }}>

Till here our app will look like this –

Replace Login Text with Lock Icon

When the button get pressed and turn into circular one, the text should hide and a lock icon replace it. To accomplish this, we need to make changes in the structure.

Now our button will hold Login text as well as lock icon. Only one will be visible at a time. When button is un pressed, text will be shown and when pressed, icon will be shown. So, they both are at the same place with opposite opacity. We can have multiple elements at a same place when they are absolutely positioned. You got the idea. Right?

This is our present Login text code –

<Text style={{
              color: '#9BE6DE',
            }}>Login</Text>

Let’s make it absolute and add lock icon part. We will keep everything compatible with Animated.

<Animated.Text style={{
  color: '#9BE6DE',
  position: 'absolute',
}}>Login</Animated.Text>

<Animated.View style={{
	  position: 'absolute',
	}}>
  <Icon name="lock" size={30} color={'#9BE6DE'} />
</Animated.View>

Both icon and Login text overlap with each other because of absolute positioning. It’s time to set the opacity of both of these so that only one could be visible according to button state. When animationVariable is 0 (i.e. button not pressed) we want text to be visible but when it is 1, then icon will be visible. See the code –

<Animated.Text style={{
	  color: '#9BE6DE',
	  position: 'absolute',
	  opacity: animationVariable.interpolate({
					inputRange: [0, 1],
					outputRange: [1, 0],
					extrapolate: 'clamp',
				}),
}}>Login</Animated.Text>
<Animated.View style={{
	  position: 'absolute',
	  opacity: animationVariable.interpolate({
					inputRange: [0, 1],
					outputRange: [0, 1],
					extrapolate: 'clamp',
				}),
	}}>
  <Icon name="lock" size={30} color={'#9BE6DE'} />
</Animated.View>

Check out the demo till this progress –

Animate a ripple from center

How a ripple works? It originates from center and reach the outermost surface or loose energy somewhere. That’s why we need a circular element which starts from zero scaling at center and reach scale 1 up to button surface. This element will be similar to Login text and lock icon. It will be placed below the lock icon so that ripple won’t hide it.

Let’s create this element –

<Animated.View style={{
                  position: 'absolute',
                  width: 60,
                  height: 60,
                  backgroundColor: '#0D2534',
                  borderRadius: 60,
                  opacity: animationVariable.interpolate({
                                inputRange: [0, 1],
                                outputRange: [0, 1],
                                extrapolate: 'clamp',
                            }),
                  transform: [{
                    scale: 0,
                  }],
                }} />

The size of our ripple element is same as circular button because it will scale up to the button surface. We set its opacity to 0 when button is not pressed.

For ripple we will need a different animation variable because this animation will run after the completion of first. Also this will run a bit slow, let say in 1 second. Check the code –

const loginButtonAnimationVariable = React.useRef(new Animated.Value(0)).current;

const runLoginButtonAnimation = (toVal) => {
    Animated.timing(loginButtonAnimationVariable, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
      easing: Easing.bounce,
    }).start();
}

Use this variable to change scale of ripple. We will get this outcome –

<Animated.View style={{
  position: 'absolute',
  width: 60,
  height: 60,
  backgroundColor: '#0D2534',
  borderRadius: 60,
  opacity: animationVariable.interpolate({
				inputRange: [0, 1],
				outputRange: [0, 1],
				extrapolate: 'clamp',
			}),
  transform: [{
	scale: loginButtonAnimationVariable.interpolate({
				inputRange: [0, 1],
				outputRange: [0.3, 1],
				extrapolate: 'clamp',
			}),
  }],
}} />

Everything is setup. Only one thing remains, running ripple animation after completion of button click animation and after completion of ripple animation revert back button animation. Look at the code –

const runAnimation = (toVal) => {
    Animated.spring(animationVariable, {
        toValue : toVal,
        useNativeDriver: false,
    }).start(() => {
      if(toVal === 1) {
        runLoginButtonAnimation(1);
      }
    });
  } 

  const runLoginButtonAnimation = (toVal) => {
    Animated.timing(loginButtonAnimationVariable, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
      easing: Easing.bounce,
    }).start(() => {
      runAnimation(0);
      loginButtonAnimationVariable.setValue(0);
    });
  }

That’s it. Completed. Check the complete demo –

This was huge but I guess you learned a lot. Take rest and we will meet in the next article.

About the Author

akamit

I am Akash Mittal, an overall computer scientist. If you want to guest post, need help in your projects, want to advertise, Feel free to contact me at [email protected]

View All Articles