Building a React Native JWT Client

Part II of JWT Auth with an Elixir on Phoenix 1.4 API and React Native

Click here for Part I, where we build the Elixir on Phoenix 1.4 JWT API referenced in this guide.

While this is Part II of the Elixir/Phoenix — React Native JSON Web Token guide, a React Native JWT client built with this guide will work with any matching API.

React Native is a powerful open-source, MIT-licensed cross-platform mobile application development framework that can be used to engineer and deliver fully native Android, Windows, and iOS apps with a single unified codebase.

Let’s build a JSON Web Token API Client with it!


Building a React Native JWT Client

Note: Since this guide is focused on JWT client functionality and AsyncStorage, we will handle navigation with simple conditional screen rendering rather than a navigation package, and we will handle state with vanilla React Native rather than Redux/Flux.

If you are familiar with the basics of React Native, skip ahead to the Planning Our App section.

For this React Native project, we will generate our codebase with the react-native-cli npm package. You will need Node, npm, and (ideally) yarn to follow along properly with this guide.

If you do not have a React Native development environment set up already, you can either follow setup instructions for native development tool installation (what we will use in this guide) or you can set up an Expo React Native development environment here.

Note: If you are working on Windows or Linux, Expo is ideal for iPhone app testing, as you can not run Xcode or its myriad phone simulators on Windows or Linux.

Let’s open up our command line and run react-native init jwt_client.

This generates a folder named jwt_client/ that contains our package.json, miscellaneous config files, and the /android + /ios directories that contain platform-specific code.

You will not need to touch your /android or /ios directories unless you need to add image/audio assets to your project, or until you are ready to configure your app for deployment to the various app stores.

You may also end up editing your /android or /ios source if you want to add advanced platform-specific code to your app, such as C++ sound mixing modules that could then be bridged to React Native. In this guide (and in most basic RN use cases), we will be coding our app entirely in React Native.

In addition to the aforementioned files, our init command also generated our initial React Native app source files, index.js and App.js.

jwt_client/index.js is the entry point for our app, where React Native's AppRegistry.registerComponent function registers our app’s root component (contained in App.js) with native app compilation processes.

We are going to need more than App.js to run our JWT client (comfortably, at least), so let’s move on to…

We are building an app that registers and authenticates users via our Elixir/Phoenix JWT API from Part I. It will be able to do the following:

  1. POST new user registrations to our /sign_up endpoint.
  2. POST existing user logins to our /sign_in endpoint.
  3. Save JWTs returned from the login/registration POSTs to device local storage.
  4. Load JWTs from device local storage.
  5. Delete JWTs from device local storage for app log out.
  6. Make authenticated GET requests.

We will be using React Native’s handy AsyncStorage functions to interact with device storage.

On startup, our app should try to load a JWT from device storage.

If device storage does not contain a JWT, our app should display a Login/Register screen.

If our app finds a JWT in device storage, our app should display an authenticated screen.

If you skipped the initialization section, run react-native init jwt_client on your CLI.

Create a folder named src in our jwt_client/ parent directory, as jwt_client/src/, then move App.js into src/.

Edit index.js to reflect this change:

‘./src/’ directory added to line 2

Create three new directories in src/:

  1. src/components/ for our React components
  2. src/screens/ for our app screen components
  3. src/services/ for our app’s local data storage functions

Create one other directory in components/, src/components/common/ for the stateless functional components we will use within our more complex stateful class components.

Create two new screen component files, screens/Auth.js and screens/LoggedIn.js .

Our Auth screen will display either a Registration form or a Login form component depending on our user’s desired state, so let’s create corresponding component files in our components/ directory, components/Registration.js and components/Login.js .

Before we start writing our main components, let’s warm up by creating a shared loading spinner component in components/common/, as common/Loading.js .

Inside Loading.js, import React from React and { View, ActivityIndicator } from React Native, then declare a functional component named Loading:

When a component does not require state or lifecycle methods, make it a functional component by declaring it with const ComponentName = (props) => { rather than with class ComponentName extends Component.

The above ActivityIndicator component transpiles to platform-specific loading animations on app compilation, and its parent View component acts as a div (for those of you coming from web dev).

Let’s pass a size prop to ActivityIndicator and give a container style to our View:

The flex: -1 sets our component’s View to its natural fixed width/height on our layout when there is enough space for it, and shrinks our view if there is not.

We will pass the size prop through our Loading component in the screens that use it, as in:<Loading size={'large'} />.

Let’s add an indexable export statement to the end of our Loading component:

Create a common component index file, as common/index.js , and export our Loading component from it:

By exporting components in a folder with index.js, we can import those components in one statement with other components indexed in this directory, e.g.: import { Loading, Button, ... } from ‘./common/'; vs import Loading from ‘./common/Loading; import Button from ‘./common/Button;’.

Now, let’s write our root component!

Open up App.js and replace the boilerplate generated by init with the following code:

At the beginning of our file, we import React, our common Loading component, and our soon-to-be-created Auth and LoggedIn screens.

We store our JWT in our root component’s state, established with this.state = { jwt: ‘’ } in our component’s constructor() .

In App’s render() function, we return our <Auth /> component if our state does not contain a JWT (if (!this.state.jwt) ) and our <LoggedIn /> component if it does.

We use an else if statement for our this.state.jwt == true case rather than a ternary because we are going to add another condition here later.

Now, let’s compose our Auth screen components!

In screens/Auth.js, write the following code:

In addition to our typical imports of React, Component, and React Native’s View, we’re importing our Login and Registration components at the beginning of this file as indexed components (we have not indexed them yet, so this will fail to compile).

We enable the use of props by passing props through our Auth component’s constructor, with constructor(props) and super(props). We establish a single piece of initial state, showLogin, which we will use to switch between our Login and Registration forms.

Our styles.container style object, passed through our component’s parent <View> tag, justifies and aligns the View’s content to the center of the screen.

Since this View is the root view for these Auth components, giving it a layout style of flex: 1 allows us to style its child views relative to each other. (Check out the React Layout Props documenation for more on Flex).

Now, add a function called whichForm() under the constructor, bind it to our component by adding this.whichForm = this.whichForm.bind(this); inside the constructor, and pass it through our View tags as {this.whichForm()}:

As written above, whichForm() checks our this.state.showLogin boolean and returns <Login /> if true or <Registration /> if false within our render() function’s return statement.

Binding whichForm() in our constructor gives it access to our state, and gives us access to this.whichForm() in other parts of our component, such as our render() function.

There are other ways to bind functions to your component, but binding in your constructor may make your bound functions easier to keep track of and your code more maintainable.

Let’s create and bind an authSwitch() function inside our component then pass it through our Registration and Login components as a prop, authSwitch={this.authSwitch}:

When run, authSwitch() sets showLogin to the opposite of its current state, or!this.state.showLogin.

Passing authSwitch() through our child components gives them access to it, thus allowing us to alter the state of our Auth component from within its children.

Before we can write our Registration and Login forms, let’s write our…

Back in our components/common/ directory, create the files common/Button.js , common/Input.js , and common/TextLink.js .

The above functional Button component renders a flex-direction: row styled View tag containing a TouchableOpacity containing a Text component.

We pass two props through our Button component, onPress (the function that runs on button press) and children (the text that renders within our Text tags).

In action, this would look like: <Button onPress={this.someFunction}> Some Button Text </Button>

We align and style our text and our button with a const styles object, and since we have more than one property on our styles object, we de-reference it inside of our component as const { button, text } = styles;.

This de-referencing allows us to run these styles through our component tags without the styles.style reference redundancy, as in<Text style={text}> instead of Text style={styles.text}>.

We can also de-reference state, ie: const { jwt, loading } = this.state;, in stateful components.

Our TextLink component code is nearly identical to our Button component, but with different styles (no backgroundColor, borderRadius, borderWidth, borderColor, etc) so that it renders as an underlined text link rather than a button.

Our Input component consists of a parent View containing a label (a styled Text component) and a TextInput component.

Because the View containing our Text label and TextInput components has the style flexDirection: 'row', Text and TextInput will display horizontally rather vertically, in a ‘row’ rather than a ‘column’.

By passing flex: 1 to our Text label and flex: 3 to our TextInput, our Text label will take up 1/4 of the row, while our TextInput will take up 3/4.( flex: 1 + flex: 3 = flex: 4).

This relative sizing with flex only works because the row containing Text and TextInput, the parent View, is styled flex: 1. A flex child’s size relative to its siblings is childFlexValue/X, where X is the sum of all siblings’ flex values.

This Input component is set to receive a lot of props, such as {label} for the displayed text child of its Text label component, and a prop for each of the following TextInput props:

  • secureTextEntry={boolean} —If true, typed characters will be obscured as asterisks (password: ********) rather than plain text (password: password).
  • placeholder={string} Placeholder text, replaced upon text entry.
  • value={string} — The value of the input field’s contents.
  • onChangeText={string} — The event/function bound to the input field, fired when the TextInput’s value changes. This is what we use to setState() in our Input’s parent components.
  • autoCorrect={boolean} — If false, auto-correct is disabled for this field.
  • multiline={boolean} — Whether or not the input will have one or multiple lines.
  • numberOfLines={integer} — If multiline={true}, this sets how many lines the input will have.

For more on TextInput props, check out the React Native docs.

Now that we’ve finished writing our common functional components, let’s export all of them from common/index.js:

Time to move on to our…

Before we start our Registration component, create the file components/index.js and export everything (*) in ./Registration from it:

Open up components/Registration.js and enter the following:

We import all of our form’s core and common components, then establish our Registration component without export default so that we may index it from our export { Registration }; statement at the end of the file.

We pass props through our constructor, then establish the following pieces of state:

  • email, password, and password_confirmation for receiving their corresponding form field input,
  • error for displaying errors, and
  • loading to hide our button and show our Loading component on form submission.

Notice how we de-reference our state with const { email, password, ...} = this.state; so we don’t have to write this.state multiple times.

Now, let’s write out our form component and establish some styles:

Notice that we add a View parent container with width: 100% form styling, and multiple View input containers with flexDirection: ‘row’ section styling.

We pass relevant props into each of our Input components, such as secureTextEntry for our password form fields, with appropriate placeholder and label props.

We also pass arrow functions into each of our Input’s onChangeText function props:

<Input ...onChangeText={password_confirmation => this.setState({ password_confirmation })} />

When the text value of these Input fields change, their corresponding state value is set to the new text value by this password_confirmation fat arrow function.

This arrow function works similarly to establishing a passwordConfirmation(){this.setState({...})) function within our component and binding it in our constructor with this.passwordConfirmation = this.passwordConfirmation.bind(this), but without all of the extra code.

While it is less code to bind functions with lambdas (arrow functions) in our child components, doing so too often will make your code less maintainable over time, so a good practice is to use self-binding arrow functions when a component is obviously setting state, such as with components containing text entry fields.

Add a Text field to display our errors at the bottom of our form View with a corresponding errorTextStyle in our styles const, and add that errorTextStyle to our styles de-referencer at the beginning of our render() function:

Below our <Text . . .> {error} </Text>component, let’s add a ternary statement that shows our Register <Button /> if our loading state is false, or a large <Loading /> component if our loading state is true:

Now that we’ve coded a bunch of components, let’s take a look at what we have so far by running react-native run-ios on our CLI!

It’s not the prettiest form, but it works!

Well, at least the fields do… our Rebecca Purple button, uh, doesn’t do too much at the moment…

Before we add our User Registration POST function to this button and finally get this React Native app some JWT action, let’s add a TextLink component under our form’s parent View.

Remember the authSwitch() function we passed to our Registration component’s props?

This TextLink component will receive our authSwitch() prop function in its onPress function prop, so that when we press this TextLink, it will switch us over to our Login component:

With our Registration components laid out, let’s see how it looks! Let’s react-native run-ios again:

Aww, shucks…

Why didn’t that work? Check the error: Adjacent JSX elements must be wrapped in an enclosing tag.., meaning our component can only have one root component.

Because our Registration component renders<View style={form}></View><TextLink...></TextLink> side-by-side, our transpiler gets confused.

Rather than create another View tag around our components, which would potentially wreck our layout, let’s import and use the handy React Fragment feature! :

Awesome.

If we run react-native run-ios again, our Registration form will render!

But if we tap our TextLink, we’ll get another error; we haven’t made our Login component yet!

Our Login form will be nearly identical to our Registration form, but with different TextLink and Button text.

Write the following in components/Login.js:

Note: The above Gist is .JSX due to a Github syntax highlighting bug in lengthy .JS files

Now, if we run our app again, our TextLinks will switch us between Login and Registration on our Auth screen!

The React Native app we have built up until this point can be viewed on the frontend-only branch of this guide’s Github repo: React-Native-JWT-Client.

That is it for this section!

Click here for Part III, Building a React Native JWT Client: API Requests and AsyncStorage

Nick West (眨眼龙)

Written by

https://nickwe.st | Distributed Systems Engineer