React Native Meteor: Auth with Email, Username, and Password

Author

Spencer Carli

Developer, cat dad, and devout pizza lover. Teaching at React Native School and building apps with Handlebar Labs.

Welcome to post #2 of my series on authentication using React Native & Meteor! This week we’re going to cover auth as it pertains to email/username + password.

It’s much more simple than last week’s post on OAuth with Facebook. It’s easier because we don’t have to deal with any native dependencies and even easier still thanks to the great work on react-native-meteor. Let’s dive in.

The full source is available here.

Make sure you’ve got React Native and Meteor installed on your system.

First thing we want to do is create our Meteor app and our React Native app.

meteor create MeteorApp && react-native init RNPasswordExample

Configuring Meteor

If you’ve used Meteor in the past you know how simple it is to setup accounts. We’re going to cover that very quickly.

cd MeteorApp/ && meteor add accounts-password accounts-ui

Then we’ll use the loginButtons template helper to quickly and easily setup accounts on our Meteor app. This is the absolute bare bones — I just want to demonstrate that we’re using the exact same accounts system.

main.html

<!-- MeteorApp/client/main.html -->

<head>
  <title>simple</title>
</head>

<body>
  <h1>Welcome to Meteor!</h1>

  {{> loginButtons}}
</body>

Well, that’s it on the Meteor side! Great isn’t it?! You’ll want to start your Meteor server (via meteor) at this point so it’s ready later one.

Configuring React Native

Now that we’ve setup our server to handle accounts let’s get things going in the React Native app.

cd RNPasswordExample && npm install --save react-native-meteor

We’ll also want to set up a few basic views. The following lays out our basic project architecture — it should be pretty straightforward.

index.*ios*.js

import React, { AppRegistry } from 'react-native';
import App from './app';

AppRegistry.registerComponent('RNPasswordExample', () => App);

RNPasswordExample/app/SignIn.js

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

class SignIn extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Sign In</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
});

export default SignIn;

RNPasswordExample/app/SignOut.js

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

class SignOut extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Sign Out</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
});

export default SignOut;

That should all be pretty vanilla React Native stuff (minus the this.data). Now we’ll start hooking up react-native-meteor.

First, let’s get some reactive data coming in — that’s where this.data comes into play. The react-native-meteor package tries to emulate the Meteor API as closely as possible so we can expect that Meteor.user will be a reactive data source.

Note: The following examples assume you’ve configured decorator support as outlined in the setup docs here.

RNPasswordExample/app/index.js

// Removed for brevity...

import Meteor, { connectMeteor } from 'react-native-meteor';

@connectMeteor
class App extends React.Component {
  componentWillMount() {
    const url = 'http://localhost:3000/websocket';
    Meteor.connect(url);
  }

  getMeteorData() {
    return {
      user: Meteor.user(),
    };
  }

  // removed for brevity...
}

export default App;

The first thing we do is connect our component (via a decorator) so that we can get access to getMeteorData. We’re then connecting to the Meteor server (which should still be running from earlier). Finally we’re using getMeteorData to setup our reactive data source. Meteor.user() will return either the currently logged in user or null and when that value changes the component will re-render. For us that means we’ll change which component is displayed.

Logging In

So now we’ve got our React Native connected to Meteor

Now that our main component is setup to show the correct component depending on whether the user is logged in or not let’s create a form that allows a user to create an account or to sign in.

RNPasswordExample/app/SignIn.js

import React, {
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  TextInput,
  Dimensions,
} from 'react-native';
const { width } = Dimensions.get('window');

class SignIn extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      email: '',
      password: '',
    };
  }

  render() {
    return (
      <View style={styles.container}>
        <TextInput
          style={styles.input}
          onChangeText={email => this.setState({ email })}
          placeholder="Email"
          autoCapitalize="none"
          autoCorrect={false}
          keyboardType="email-address"
        />

        <TextInput
          style={styles.input}
          onChangeText={password => this.setState({ password })}
          placeholder="Password"
          autoCapitalize="none"
          autoCorrect={false}
          secureTextEntry={true}
        />

        <TouchableOpacity style={styles.button}>
          <Text style={styles.buttonText}>Sign In</Text>
        </TouchableOpacity>

        <TouchableOpacity style={styles.button}>
          <Text style={styles.buttonText}>Create Account</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

const ELEMENT_WIDTH = width - 40;
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  input: {
    width: ELEMENT_WIDTH,
    fontSize: 16,
    height: 36,
    padding: 10,
    backgroundColor: '#FFFFFF',
    borderColor: '#888888',
    borderWidth: 1,
    marginHorizontal: 20,
    marginBottom: 10,
  },
  button: {
    backgroundColor: '#3B5998',
    width: ELEMENT_WIDTH,
    padding: 10,
    alignItems: 'center',
    marginBottom: 10,
  },
  buttonText: {
    color: '#FFFFFF',
    fontWeight: '500',
    fontSize: 16,
  },
});

export default SignIn;

That’s just creating a very basic for with two inputs (email, password) and two buttons (sign in, create account). Easy enough.

We’re also going to add some very basic form validation so our user doesn’t have to wait for a round trip to the server to check if they entered a password or not. We’ll also tack on some onPress handlers to our buttons.

RNPasswordExample/app/SignIn.js

// Removed for brevity
class SignIn extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      email: '',
      password: '',
      error: null, // added this
    };
  }

  isValid() {
    const { email, password } = this.state;
    let valid = false;

    if (email.length > 0 && password.length > 0) {
      valid = true;
    }

    if (email.length === 0) {
      this.setState({ error: 'You must enter an email address' });
    } else if (password.length === 0) {
      this.setState({ error: 'You must enter a password' });
    }

    return valid;
  }

  onSignIn() {
    const { email, password } = this.state;

    if (this.isValid()) {
      // do stuff
    }
  }

  onCreateAccount() {
    const { email, password } = this.state;

    if (this.isValid()) {
      // do stuff
    }
  }

  render() {
    return (
      <View style={styles.container}>
        // removed for brevity
        <Text style={styles.error}>{this.state.error}</Text>
        <TouchableOpacity
          style={styles.button}
          onPress={this.onSignIn.bind(this)}
        >
          <Text style={styles.buttonText}>Sign In</Text>
        </TouchableOpacity>
        <TouchableOpacity
          style={styles.button}
          onPress={this.onCreateAccount.bind(this)}
        >
          <Text style={styles.buttonText}>Create Account</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

Okay let’s log them into our Meteor server now! One thing to note about this snippet — I noticed what seems to be a bug when creating an account that the user won’t automatically be logged in so line 29 will likely be unnecessary in the future. You’ll also notice we’re catching a displaying the Meteor error, if one exists.

RNPasswordExample/app/SignIn.js

// Removed for brevity
import Meteor, { Accounts } from 'react-native-meteor';

class SignIn extends React.Component {
  // Removed for brevity

  onSignIn() {
    const { email, password } = this.state;

    if (this.isValid()) {
      Meteor.loginWithPassword(email, password, error => {
        if (error) {
          this.setState({ error: error.reason });
        }
      });
    }
  }

  onCreateAccount() {
    const { email, password } = this.state;

    if (this.isValid()) {
      Accounts.createUser({ email, password }, error => {
        if (error) {
          this.setState({ error: error.reason });
        } else {
          this.onSignIn(); // temp hack that you might need to use
        }
      });
    }
  }

  // Removed for brevity
}

export default SignIn;

Logging Out

We also want to give the user the option to sign out. This creates the exact same button as earlier but when it’s pressed we’re calling Meteor.logout()

Note: If this was a real app you would likely want to create a button component. I didn’t here to keep things simple and to stay focused on the subject at hand.

RNPasswordExample/app/SignOut.js

import React, {
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  Dimensions,
} from 'react-native';
import Meteor from 'react-native-meteor';
const { width } = Dimensions.get('window');

class SignOut extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <TouchableOpacity style={styles.button} onPress={() => Meteor.logout()}>
          <Text style={styles.buttonText}>Sign Out</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

const ELEMENT_WIDTH = width - 40;
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  button: {
    backgroundColor: '#3B5998',
    width: ELEMENT_WIDTH,
    padding: 10,
    alignItems: 'center',
    marginBottom: 10,
  },
  buttonText: {
    color: '#FFFFFF',
    fontWeight: '500',
    fontSize: 16,
  },
});

export default SignOut;