Introduction

React Native is a hybrid 'native' JavaScript framework for building mobile applications. It is basically a combination of JavaScript and native mobile application concepts.
Hybrid applications bring the benefit of a shared code base between iOS and android mobile applications as well as web applications.

This tutorial will go through a step by step process of coming up with a login page using the React Native framework within an environment setup in Linux(Ubuntu)and Android development.

Take Aways

By the end of this tutorial, the following concepts will be covered:

  1. React Native Project Set Up
  2. Text Input and Validation
  3. Text and Button Events
  4. State
  5. Conditional Rendering of Components
  6. Basic Routing
  7. Running and Building the App

Prerequisites

  1. Knowledge of JavaScript

Environment Set Up

These are the 4 required tools:

  • Node
  • React Native Command Line Interface
  • JDK
  • Android Studio

Setup your environment through:

  1. Follow this link to find the relevant installation command for node LTS version.

# As root
curl -sL https://rpm.nodesource.com/setup_lts.x | bash -

# No root privileges 
curl -sL https://rpm.nodesource.com/setup_lts.x | sudo bash -

Verify your installation using:

    node --version
  1. Install version 8 (or newer) JDK

Verify your installation using:

java --version
javac --version
  1. Download Android Studio

Extract the zipped android studio folder into your {preferred folder}.
Go into the {preferred folder}/android-studio/bin directory and run android studio with the following command:

./studio.sh 

  1. Select these options and click finish installation
  • Android SDK
  • Android SDK Platform
  • Android Virtual Device
  1. Set up SDK

Click configure and open the SDK Manager. (If you cannot see the configure option, expand your Adnroid studio window to full screen)

Expand Android 10(Q) and select by checking the Show Package Details checkbox.

  • Android SDK Platform 29
  • Intel x86 Atom_64 System Image or Google APIs Intel x86 Atom System Image

Deselect any other selected Android SDK that is not 10(Q)

On the SDK tools tab, check Show package details and select 29.0.2 only.

Click Apply, Accept and Finish to complete SDK set up and installation.

  1. Set Up Android Home

Find the Andorid SDK path from the Android Studio in the Appearance & Behavior → System Settings → Android SDK Location.

Add these lines to your $HOME/.bash_profile using an editor such as nano i.e.

nano $HOME/.bash_profile

export ANDROID_SDK_ROOT=$HOME/Android/Sdk
export ANDROID_HOME=$HOME/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools

Load the Configuration

source $HOME/.bash_profile

Verify the configuration and compare with the path Android SDK path you had found above

echo $PATH

Step 1. Create the Project

Use npx to access the React Native CLI and npx comes with node.
This will ensure we use the latest stable version of react native CLI at the time of executing this command.

Do NOT install react native CLI globally.
If you had installed a global react native CLI, uninstall it.

Run the command below in your preferred projects directory.
A folder with the project name will be created.

    npx react-native init ReactLogin 

Step 2. Prepare your Device

For Android users:
Enable Debugging by going to Settings → About phone → Software information and then tapping the Build number row at the bottom seven times.

Go back to Settings → Developer Options and enable "USB debugging"

Connect your Mobile Device to your computer.

Verify the connection with the following command:

 adb devices

To use a virtual device:
Under Configure -> AVD Manager in the android studio, create a new device using the Q API Level 29 image.

Launch the emulator.

Possible Errors

  • dev/kvm device permission denied

Follow this article to correct this error.

  • dev/kvm device permission denied

Follow this article to correct this error.

3. Run the Project

Open two Terminals. Go to the root folder of the project and run command as follows:

In Terminal 1 start the Metro using the following command:

npx react-native start

In Terminal 2 start the project or run the app using the following command:

npx react-native run-android

Possible Errors

  1. No online devices found
  2. Connection not allowed

Ensure your device is well connected, with a fair cable and the USB mode is charging only (or debugging for older devices).

4. Understand the Folder Structure

├── App.js
├── app.json
├── ios/
├── index.js
├── android/
├── package.json
├── node_modules/
└──__tests__
. └── App-test.js

The App.js contains the first component that renders on App start.

The Android (or iOS) folder contains settings native to android (or iOS).

The package.json contains the packages that are used in the project.

5. Define your Folder Structure

Add a new folder called src with the following files and directories:

.
├── App.js
├── components
│   ├── elements/
│   └── pages/
└── Router.js

The Router.js file will be used to Register all components that will serve as Pages thus enabling routing.

The App.js file will be used to render the Router Component.

The pages folder will contain components that will save as full page views.

The elements folder will store components that will not form full pages but will be imported by the page components.

6. Update the index.js

Update index.js to render the new app component in the src/App.js file we created.


import {AppRegistry} from 'react-native';
import App from './src/App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

7. Set up the Routes

Set up native routing by installing the following required dependencies using the commands:


 npm install --save @react-navigation/native

 npm install --save react-native-screens
 
 npm install --save react-native-gesture-handler 
 
 npm install --save react-native-reanimated 

Install the react-native-router-flux that will handle routing using the command:


  npm install --save react-native-router-flux
  

In the src/Router.js add the following code to help set up View Scenes.
The scene with the initial prop will always render first.
The hideNavBar prop in the code below is used to hide the default header that comes with react-native-router-flux.


import React from 'react';
import { Scene, Router } from 'react-native-router-flux';

import LoginPage from './components/pages/loginPage';
import HomePage from './components/pages/homePage';

const RouterComponent = () => {

    return (
        <Router>
            <Scene key="root" hideNavBar>

                <Scene key="all">

                    <Scene
                        key="loginPage"
                        component={loginPage}
                        hideNavBar
                        initial
                    />

                    <Scene
                        key="homePage"
                        component={homePage}
                        hideNavBar
                    />

                </Scene>

            </Scene>
        </Router>
    );

};

export default RouterComponent;

8. Set up App.js to Render Routes.js

Since the src/Routes.js is responsible for routing between different page components, it is the only component that will be rendered by the src/App.js.

Define App.js as follows:


import React, { Component } from 'react';

import Router from './Router';


class App extends Component {

    render() {

        return (

            <Router />

        );
    }
}

export default App; 

9. Create Minimalistic Login Page

In the src/components/pages folder create a LoginPage.js file.

Define it as follows:

The StatusBar customizes the status bar.
The SafeAreaView is React Native's way of dealing with mobile devices with a notch.

The Stylesheet is used to avoid creation of new style objects every time a component renders.
The styles objects are destructured and passed to the View as a prop.

A prop is a parameter for a component.


import React, { Component } from 'react';

import {
    View,
    StyleSheet,
    StatusBar,
    SafeAreaView,
    Dimensions
} from 'react-native';



class LoginPage extends Component {

    constructor() {
        super();
    }

    render() {

        const {
            mainContainer,
        } = styles;

        return (

            <>
                <StatusBar backgroundColor="#ef473a" barStyle="light-content" />
                <SafeAreaView style={{ flex: 1 }}>
                    <View style={mainContainer}>


                    </View>
                </SafeAreaView>
            </>

        );
    }
}


export default LoginPage;


const styles = StyleSheet.create({

    mainContainer: {
        flex: 1,
        backgroundColor: '#f1f0f2'
    },

})

Step 10. Create Minimalistic Home Page

Copy the above code snippet to the src/components/pages/HomePage.js

Remember to change the name of the class from LoginPage to HomePage

Run The App. You can refer to step 3 above

Possible Errors

  • Error: ENOSPC: System limit for number of file watchers reached

Fix that by increasing the number of files watched

sudo nano /etc/sysctl.conf

Add this line to that file

fs.inotify.max_user_watches=524288

Load the configuration by running the following command:

sudo sysctl -p

More details about this Error can be found here

  • SDK location not found
    Reload the env variables using the command:
source $HOME/.bash_profile
  • com.android.builder.testing.api.DeviceException: No connected devices!
    Refer to step 2 above

Step 11. Create the Text Input Form

Create a single reusable text input element that will be imported in the LoginPage and reused to take the email and password inputs, a concept known as reusable components.

We will use the following props to customize the text inputs independently

  1. OnchangeText -> will be called whenever there is a change in the input text.
  2. SecureTextEntry -> will be used to hide password inputs.

import React, { Component } from 'react';

import {
    View,
    StyleSheet,
    TextInput,
    Dimensions
} from 'react-native';



class CustomTextInput extends Component {

    constructor(props) {
        super(props);
    }

    render() {

        const {
            textInputContainer,
            textTextInputSpecs
        } = styles;

        return (


            <View style={textInputContainer}>
                <TextInput
                    placeholder={this.props.placeholder}
                    placeholderTextColor="rgba(204, 50, 50, 0.56)"
                    value={this.props.inputText}
                    onChangeText={value => this.props.onchangeText(value)}
                    style={textTextInputSpecs}
                    keyboardType={this.props.keyboardType}
                    secureTextEntry={this.props.secureTextEntry}

                />

            </View>

        );
    }
}



export default CustomTextInput;

const {
    height, width
} = Dimensions.get('screen');


const styles = StyleSheet.create({

    textInputContainer: {
        width: width * 0.75,
        backgroundColor: 'white',
        borderRadius: width * 0.1,
        marginVertical: height * 0.0125,
        alignItems: 'center',
        justifyContent: 'center',
    },

    textTextInputSpecs: {
        color: '#cb2d3e',
        fontSize: height * 0.03,
        paddingVertical: height * 0.0125,


    },
})

Step 12. Import the Text Input Element as Shown Below

In the LoginPage, add the following importation, state, function and style:


import
CustomTextInput
    from '../elements/CustomTextInput';
    
    ...
    
constructor() {
    super();
    this.onchangeText = this.onchangeText.bind(this);
}

state = {
    emailText: '',
    passwordText: ''
}

 onchangeText(what, value) {

    if (what === this.state.emailText) {

        this.setState({ emailText: value });

    } else {

       this.setState({ passwordText: value });

    }

}

...

<View style={mainContainer}>

    <CustomTextInput
        placeholder=" Email "
        onchangeText={this.onchangeText}
        keyboardType="email-address"
        secureTextEntry={false}
        value={this.state.emailText}


    />

    <CustomTextInput
        placeholder=" Password "
        onchangeText={this.onchangeText}
        keyboardType="default"
        secureTextEntry={true}
        value={this.state.passwordText}
    />

</View>

...

const styles = StyleSheet.create({

    mainContainer: {
          ...
        justifyContent: "flex-end",
          ...
    },

})

If you run your app, you should have something like this:

The Login Page of the Project's React Native App

13. Create the Button Element

Just like in step 11 above in the src/elements/CustomButton.js file, define a reusable button as follows:


import React, { Component } from 'react';

import {
    TouchableOpacity,
    View,
    Text,
    Dimensions,
    StyleSheet
} from 'react-native';


class CustomButton extends Component {

    constructor(props) {
        super(props);
    }

    render() {

        const {
            letsGoButtonContainer,
            letsGoButtonText
        } = styles;

        return (


            <TouchableOpacity
                onPress={this.props.onLetsGoPress}
            >
                <View
                    style={letsGoButtonContainer}
                >

                    <Text style={letsGoButtonText}>
                        Cool Lets Go
                </Text>

                </View>
            </TouchableOpacity>

        );
    }
}



export default CustomButton;

const {
    height, width
} = Dimensions.get('screen');


const styles = StyleSheet.create({

    letsGoButtonContainer: {
        width: width * 0.45,
        borderRadius: width * 0.1,
        borderRadius: width * 0.1,
        marginVertical: height * 0.0125,
        paddingVertical: height * 0.0125,
        backgroundColor: '#ef473a',
        alignItems: 'center',
        justifyContent: 'center',

    },
    letsGoButtonText: {
        fontSize: height * 0.03,
        color: 'white',

    },
})

14. Import the Custom Button Element

Just like in step 12 above, import as follows to LoginPage:


import CustomButton from '../elements/CustomButton';

...

constructor() {
    ...
    this.onLetsGoPress = this.onLetsGoPress.bind(this);
}

state = {
    ...
    buttonLoading: false
}

...

onLetsGoPress() {

    //set button to loading
    this.setState({
        buttonLoading: true
    })

    //set button to Not loading after 5 seconds
    setTimeout(() => {
        this.setState({
            buttonLoading: false
        })
    }, 5000);

}

...

<View style={mainContainer}>

 ...

    <CustomButton
        onLetsGoPress={this.onLetsGoPress}
        buttonLoading={this.state.buttonLoading}
    />

</View>

...

Step 15. Add a Spinner when buttonLoading state is set to true

To avoid multiple presses, replace your button with a spinner until the button activity is completed.

We used the setTimout function above to simulate a time consuming activity.
In a real application this would probably be a http request to a remote API.

The concept of conditional component rendering is used here.

Update the CustomButton Component to accommodate the Spinner as follows:


import React, { Component } from 'react';

import {
    TouchableOpacity,
    View,
    Text,
    Dimensions,
    StyleSheet,
    ActivityIndicator
} from 'react-native';



class CustomButton extends Component {

    constructor(props) {
        super(props);
    }

    renderButtonorSpinner() {

        const {
            buttonLoading
        } = this.props;

        const {
            letsGoButtonContainer,
            letsGoButtonText,
            spinnerContainer
        } = styles;

        if (buttonLoading) {
            return (
                <View style={spinnerContainer}>
                    <ActivityIndicator
                        size="large"
                        color="#cb2d3e"
                    />

                </View>
            );
        } else {

            return (
                <TouchableOpacity onPress={this.props.onLetsGoPress}>
                    <View style={letsGoButtonContainer}>

                        <Text style={letsGoButtonText}>
                            Cool Lets Go
                        </Text>

                    </View>
                </TouchableOpacity>
            );

        }

    }

    render() {

        return this.renderButtonorSpinner();

    }
}



export default CustomButton;

const {
    height, width
} = Dimensions.get('screen');


const styles = StyleSheet.create({

    letsGoButtonContainer: {
        width: width * 0.45,
        borderRadius: width * 0.1,
        borderRadius: width * 0.1,
        marginVertical: height * 0.0125,
        paddingVertical: height * 0.0125,
        backgroundColor: '#ef473a',
        alignItems: 'center',
        justifyContent: 'center',

    },
    spinnerContainer: {
        width: width * 0.45,
        borderRadius: width * 0.1,
        borderRadius: width * 0.1,
        marginVertical: height * 0.0125,
        paddingVertical: height * 0.0125,
        alignItems: 'center',
        justifyContent: 'center',

    },
    letsGoButtonText: {
        fontSize: height * 0.03,
        color: 'white',

    },
})

Step 16. Route to the Next Page

Once the dummy 'Login Activity' we created using the setTimeout function is complete, we need to route to the homepage.

The react-native-router flux package has an easy way of achieving this.

The Actions Object will be used to call the HomePage as a function.
This was enabled by the key prop in step 7 above.

In the LoginPage add the following to accommodate routing:

import { Actions } from 'react-native-router-flux';


onLetsGoPress() {

     ...
    setTimeout(() => {
        Actions.HomePage();
        this.setState({
            buttonLoading: false
        })
    }, 5000);

}

Step 17. Validate Inputs and Set up a Decent Login Error Message

Render an error message depending on the occurrence of an error in the LoginPage.

Add a loginErrorMessage key to hold the error message.

Define a displayErrorMessage() function that will render the error message.

Define a validateInput() to perform some checks on the input text.

Update the onLetsGoPress() to use the validate method.

Render the message by calling this.displayErrorMessage() in the main View as shown below:

...
state = {
  ...
   loginErrorMessage: ''
}
...

displayErrorMessage() {
    const {
        loginErrorText
    } = styles;

    if (this.state.loginErrorMessage !== '') {
        return (
            <Text style={loginErrorText}>
                {this.state.loginErrorMessage}
            </Text>
        );
    }
}
...
validateInput() {


    if (this.state.emailText === '') {
        this.setState({
            loginErrorMessage: 'Email Cannot be Empty',
            buttonLoading: false
        });
        return false;
    }

    if (this.state.passwordText === '') {
        this.setState({
            loginErrorMessage: 'Password Cannot be Empty',
            buttonLoading: false
        });
        return false;
    }

    this.setState({
        loginErrorMessage: '',
        buttonLoading: true
    });
    return true;

}

...

onLetsGoPress() {

    if (this.validateInput()) {

        setTimeout(() => {
            Actions.HomePage();
            this.setState({
                buttonLoading: false
            })

        }, 5000);

    }

}

...

<CustomTextInput
    placeholder=" Password "
    onchangeText={this.onchangeText}
    keyboardType="default"
    secureTextEntry={true}
    value={this.state.passwordText}
    what="pass"
/>
{this.displayErrorMessage()}
<CustomButton
    onLetsGoPress={this.onLetsGoPress}
    buttonLoading={this.state.buttonLoading}
/>

...

const styles = StyleSheet.create({
    ...
    loginErrorText: {
        color: 'red'
    }

})


Step 18. Pass Variables to the HomePage

Passing variables to the next page is a common requirement.

Data stores such as context or Redux can aid in this.

For this example, we will pass the variables as props.

Update the LoginPage to pass props to the HomePage as follows:

...

Actions.HomePage({ user: this.state.emailText, password: this.state.passwordText });

...

The variables can be accessed as props in the HomePage as shown below:

...
    this.props.user
    
    {this.props.user}
    
...
    

Step 19. Conclusion and Next Steps

This Login Page tutorial has not only explained how to come up with the login page but has also covered most of the basic base concepts for React Native.
The styling especially on dimensions is very targeted to help you avoid 'element sizing guesswork' and will accommodate any screen size used.

For the next steps populate the homepage and try to build a debug and release App(an app to install and test in different devices).

React Native is an easy and quick to use JavaScript framework that is relevant in developing and building cross-platform applications.

Full code for the project can be found on Github

You've successfully subscribed to Decoded For Devs
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Your link has expired
Success! Your account is fully activated, you now have access to all content.