Building a Login Form in React Native with Redux

In my last post, I got everything ready for creating a React Native app. If you haven’t tried it yet, go back now and get something working.

Today, I want to produce a small app. The app will display a login form and a secured form, using native controls. Everything is built on top of React Native, but I’m going to use Redux for the application state management. I’ve written about Redux before, so this should be a refresher.

I’m going to assume you are using Visual Studio Code and have installed the React Native Tools for Visual Studio Code. If you haven’t already done so, go and download it, because the tooling that is available here is remarkable. Most importantly, it allows you to attach a debugger to the running code in the emulator, which is awesome.

Each platform has its own start up file. The Android one is called index.android.js and the iOS one is called index.ios.js. I’ve altered mine to say the following:

import React, { Component } from 'react';
import { AppRegistry } from 'react-native';
import { Provider } from 'react-redux';
import Application from './pages/Application';
import store from './redux';

export default class RNCognito extends Component {
  render() {
    return (
      <Provider store={store}>
        <Application />
      </Provider>
    );
  }
}

AppRegistry.registerComponent('RNCognito', () => RNCognito);

Both files are the same. Sometimes, you want the startup to be different between the two platforms, but this isn’t one of those times. I haven’t created the Redux store as yet, so that’s my next stop. Use yarn to install the react-redux and redux packages:

yarn add react-redux redux

Redux requires some actions (stored in redux/actions/auth.js – you will have to create the directories):

export const login = (username, password) => {
    return {
        type: 'LOGIN',
        username: username,
        password: password
    };
};

export const logout = () => {
    return {
        type: 'LOGOUT'
    };
};

export const signup = (username, password) => {
    return (dispatch) => {
    };
};

I’ve not put anything in the signup yet, since that’s going to come in a later post. However, I should be able to toggle between the login form and the signup form after I am done. Redux also requires a reducer. My redux\reducers\auth.js:

const defaultState = {
    isLoggedIn: false,
    username: '',
    password: ''
};

export default function reducer(state = defaultState, action) {
    switch (action.type) {
        case 'LOGIN': 
            return Object.assign({}, state, { 
                isLoggedIn: true,
                username: action.username,
                password: action.password
            });
        case 'LOGOUT':
            return Object.assign({}, state, { 
                isLoggedIn: false,
                username: '',
                password: ''
            });
        default:
            return state;
    }
}

I also have a redux\reducers\index.js that combines all the reducers (all one of them right now, but this will expand) into one:

import { combineReducers } from 'redux';
import auth from './auth';

const rootReducer = combineReducers({
    auth
});

export default rootReducer;

Finally, I’ve got a store definition in redux\index.js:

import { createStore } from 'redux';
import rootReducer from './reducers';

let store = createStore(rootReducer);

export default store;

This is a fairly basic Redux implementation. In fact, it’s as basic as it gets, but then again, I don’t need much here. My components will dispatch the login() or logout() action creators; the reducer will update the auth store, and the whole application will be re-rendered, just like any other React+Redux application.

On to my components! From the index.android.js file, I have an pages/Application.js component. This will load one of two pages depending on if I am logged in:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import Login from './Login';
import Secured from './Secured';

class Application extends Component {
    render() {
        if (this.props.isLoggedIn) {
            return <Secured />;
        } else {
            return <Login />;
        }
    }
}

const mapStateToProps = (state, ownProps) => {
    return {
        isLoggedIn: state.auth.isLoggedIn
    };
}

export default connect(mapStateToProps)(Application);

Again – this is all vanilla Redux container configuration. There is nothing special here – just straight up React code. When I get to the two referenced components, however, things change. I’m going to use native controls:

I highly recommend spending some time with the API documentation at this point. Each component is thoroughly documented and there are loads of options.

The first screen I’m going to see is the pages/Login.js component:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ScrollView, Text, TextInput, View, Button } from 'react-native';
import { login } from '../redux/actions/auth';

class Login extends Component {
    constructor (props) {
        super(props);
        this.state = {
            route: 'Login',
            username: '',
            password: ''
        };
    }

    userLogin (e) {
        this.props.onLogin(this.state.username, this.state.password);
        e.preventDefault();
    }

    toggleRoute (e) {
        let alt = (this.state.route === 'Login') ? 'SignUp' : 'Login';
        this.setState({ route: alt });
        e.preventDefault();
    }

    render () {
        let alt = (this.state.route === 'Login') ? 'SignUp' : 'Login';
        return (
            <ScrollView style={{padding: 20}}>
                <Text style={{fontSize: 27}}>{this.state.route}</Text>
                <TextInput 
                    placeholder='Username' 
                    autoCapitalize='none' 
                    autoCorrect={false} 
                    autoFocus={true} 
                    keyboardType='email-address' 
                    value={this.state.username} 
                    onChangeText={(text) => this.setState({ username: text })} />
                <TextInput 
                    placeholder='Password' 
                    autoCapitalize='none' 
                    autoCorrect={false} 
                    secureTextEntry={true} 
                    value={this.state.password} 
                    onChangeText={(text) => this.setState({ password: text })} />
                <View style={{margin: 7}}/>
                <Button onPress={(e) => this.userLogin(e)} title={this.state.route}/>
                <Text style={{fontSize: 16, color: 'blue'}} onPress={(e) => this.toggleRoute(e)}>{alt}</Text>
            </ScrollView>
        );
    }
}


const mapStateToProps = (state, ownProps) => {
    return {
        isLoggedIn: state.auth.isLoggedIn
    };
}

const mapDispatchToProps = (dispatch) => {
    return {
        onLogin: (username, password) => { dispatch(login(username, password)); },
        onSignUp: (username, password) => { dispatch(signup(username, password)); }
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(Login);

Most of this is actually standard React code. The sub-components are React Native components. The important things to note are:

  • How events are handled. Note that I use an arrow function to pass the event into the class. If you don’t, this is in the context of the Button, not your component.
  • Look at all the options on the TextInput. Everything you can do on a native text field is available on the React Native version.
  • Note that I wrap the React component in a standard Redux container wrapper to get the variables I want into the props of my component.

The pages/Secured.js component is my secure area:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ScrollView, Text, View, Button } from 'react-native';
import { logout } from '../redux/actions/auth';

class Secured extends Component {
    userLogout(e) {
        this.props.onLogout();
        e.preventDefault();
    }
    
    render() {
        return (
            <ScrollView style={{padding: 20}}>
                <Text style={{fontSize: 27}}>
                    {`Welcome ${this.props.username}`}
                </Text>
                <View style={{margin: 20}}/>
                <Button onPress={(e) => this.userLogout(e)} title="Logout"/>
            </ScrollView>
        );
    }
}


const mapStateToProps = (state, ownProps) => {
    return {
        username: state.auth.username
    };
}

const mapDispatchToProps = (dispatch) => {
    return {
        onLogout: () => { dispatch(logout()); }
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(Secured);

That’s all the code! Fire up the Google Android Emulator, but don’t run the app just yet!

Using the React Native Debugger

One of the neat features of Visual Studio Code is the debugger. Click this button on the sidebar:

The first thing to do is to add a React Native launch configuration. Click the drop-down next to DEBUG and select Add Configuration:

Select the React Native configuration. You don’t have to add any other configurations as they are all created for you. Just save the launch.json. The Debug Android section should be selected next to DEBUG now. All you have to do is click the green start button. Once started, you should see the following screen:

Enter something in the email address field. Note that the email address keyboard is selected. Enter something in the Password field, and note that the content is hidden. Clicking on Login should swap you over to the Secured page, where you will see “Welcome username” (replacing the username with whatever you typed).

Going one step deeper. Stop the app (from within Visual Studio, click the red stop button at the top). Open the redux/reducers/auth.js file and set a breakpoint at the switch statement. A red circle will appear at line 8:

Now, start your app again. Go through the login process again. This time, the execution stops and you can inspect the state of your store:

Click on the green play button to continue, or try stepping through the code, just like any other debugger.

I’ll have more to say about this app in my next blog post. For now, you can see the code on my GitHub Repository.