React Native And Redux — Part 5

Anuj Baranwal
13 min readJun 25, 2018

In the previous parts, we discussed about set up for react native, react native components, firebase, redux, redux with react native, and created few app with it

Part 1 — https://medium.com/@anujbaranwal/react-native-and-redux-part-1-754c13a440ba

Part 2 — https://medium.com/@anujbaranwal/react-native-and-redux-part-2-14c108a65390

Part 3 — https://medium.com/@anujbaranwal/react-native-and-redux-part-3-cbd61e6b303

Part 4 — https://blog.usejournal.com/react-native-and-redux-part-4-be182ad3fc10

Git Repo for my part(1–4) blogs —

Repo 1 — https://github.com/anuj070894/react_native_medium_1

Repo 2 — https://github.com/anuj070894/react_native_medium_2

Repo 3 — https://github.com/anuj070894/react_native_medium_3

Repo used in this Part — https://github.com/anuj070894/react_native_medium_4

In this part, we will create a app which will encompass all the concepts so far and will include a major component that has been missing so far, Navigation

So, let’s see what we will be building here —

This is an app called Manager. A manager will be able to login and add players to its team. Once the manager is successfully signed in, he/she can see the list of players that he has added so far in the team. He/she can either add a new player (or) go to a specific player. Then he/she will see the third screen, where details can be edited and saved, or can send a text message for schedule with schedule button, and remove the player. There is a back button for navigation. Upon clicking on the remove button, he/she will see the fourth screen, sorta overlay on the top of the whole screen. And we will use the firebase to show the list of players that were entered by a specific manager.

To create this app, first we need to do some set up which we have been following in the past parts —

Steps —

Branch — 01_Init(src/App.js, index.js, src/reducers/index.js)

Implementing the app

  • Login Page — the login page(component) can have four states, e.g., Email, Password, Error, Loading. In earlier part, where we created the auth app, we saw that the logic to maintain the loading and error part was put in the component level itself. But with redux we can separate this logic of handling the attempt to do login and return new states from redux — loading, error so that a component need not worry about the logic of how the login is happening and it only has to render the jsx and redux is to handle the decision making inside our application

Let’s start creating the LoginForm. We will be creating a new LoginForm.js, but however, we will still be reusing the components from https://github.com/anuj070894/react_native_medium_3/tree/master/src/common and putting it inside our app src/components/common

in src/components/LoginForm.js

import React, { Component } from 'react';import { Card, CardSection, ButtonComponent, Input } from './common';class LoginForm extends Component {emailChanged = () => {}render() {return (<Card><CardSection><Inputlabel="Email"onChangeText={this.emailChanged}placeholder="user@gmail.com"/></CardSection>
<CardSection>
<Inputlabel="Password"secureTextEntryonChangeText={this.passwordChanged}placeholder="password"/></CardSection>
<CardSection><ButtonComponenttext="Log In"/></CardSection></Card>);}}export default LoginForm;

Import it in src/App.js and verify that no error is coming. Before doing the redux part, let’s discuss what steps we need to do and how we are going to implement it with the redux that we have learned in the previous part —

Steps —

  • User types something
  • call Action Creator with new text — creating a new action creator inside actions/index.js
export const emailChanged = (text) => {return {type: 'email_changed',payload: text,};}
  • action creator returns an action — a plain javascript object containing the type and payload
  • action sent to all reducers — happens because of connect(null, actions) to component

export default connect(null, actions)(LoginForm);

  • reducer calculates new app state
const INITIAL_STATE = { email: '' };export default (state, action) => {switch (action.type) {case 'email_changed':// return '';default:return state;}}
  • state sent to all components — will discuss later
  • components rerender with new state — will discuss later

… wait for change … steps repeat …

Importance of types.js

As you can see above in step 2 and step 5, we placed a hard coded string ‘email_changed’ at two places. As these strings are really important and a change in it at one of the places will really break the flow, therefore, it is better to place them at a central place. Therefore, we use a file src/actions/types.js to hold all the strings together

in src/actions/types.js

export const EMAIL_CHANGED = 'email_changed';

Before we move forward, let’s talk a bit about the immutability of the state in the reducer and why is it important?

Immutable state

A reducer takes a slice of state and an action as parameter and returns a new slice of state. This is pretty much what is does

So, when the reducer is just about to return the new state, it checks whether the new state and old state are equal or not. If they are equal, then nothing has changed. Else something changed and the interested components should update.

Check that there is a three equal operator to verify between the newState and the oldState. So, if we do something like state.email = 'anuj@gmail.com'; it will not create a new object, and both the newState and oldState will remain the same

const a = { 1: 1};
const b = a;
b[1] = 2;
a === b; // true

So, we have to respect the immutability of state and figure out a way to avoid assigning directly to the property of state object. The best way is to return a new object altogether by destructuring the object and assign the property a value at the end so that it overrides any previous such property

return { ...state, email: action.payload };// test example
const a = { 1: 1};
const b = { ...a };
b[1] = 2;
a === b; // false

in src/reducers/AuthReducer.js

import { EMAIL_CHANGED } from '../actions/types';const INITIAL_STATE = { email: '' };export default (state = INITIAL_STATE, action) => {switch (action.type) {case EMAIL_CHANGED:return { ...state, email: action.payload };default:return state;}}

Now, all we have to do is connect the actions and global state object in LoginForm.

Similarly, we can do the same for password. I think this is a good step to put a new branch for testing. Branch — 02_Init

So, now, we are at a stage where the password and email aligns perfectly with the redux way. But, when the user clicks on the Login button, we need to make a call to firebase and it returns some state after sometime. This call is asynchronous. But our actionCreator, right now, is synchronous. It returns the plain action javascript object almost immediately.

Current State — email, password

Proposed State — email, password, loading, error, user

Proposed state

  • email — when the user types something in the email field
  • password — when the user types something in the password field
  • loading — true when we start an auth request, false when it is complete
  • error — default to empty string, toss in an error message when we get a failed auth request
  • user — default to null. put in the user model when user successfully auth

The states that will be returned when the user clicks the Login Button could be one of the loading, error, user but error and user are not immediately returned but returned sometime later. So, we need to handle it in that way. So far our action creators are —

  • call to action creator
  • action creator runs
  • action creator returns action

With firebase in action,

  • call to action creator
  • action creator runs
  • req to firebase is made
  • we have nothing to return immediately
  • at some point of time later —req made to firebase is completed, we can return action

To implement an asynchronous action creator, we will make use of library called redux-thunk. So, to install it —

npm install --save redux-thunk

Redux-Thunk actually allows to bend the default rules of action creators so that they can be made asynchronous. Here is how it does that —

Default rules of an action creator —

  • action creators are functions
  • must return an action
  • action is an object with a ‘type’ property

Action creator with redux-thunk

  • action creators are functions
  • must return a function
  • this function will be called with dispatch(similar to what we saw in redux tutorial, where store.dispatch is called and it goes to all the reducers)

Redux Thunk is what we call as enhancers in terms in of redux. To wire it up in our app, we need to import it in src/App.js and create a store with these enhancers —

import ReduxThunk from 'redux-thunk';

const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));

Let’s discuss how redux-thunk fits in our app when LoginButton is clicked —

Steps —

  • action creator called(loginUser(email, password))
  • action creator returns a function
  • redux thunk sees that we return a function and calls it with dispatch
  • we do our login request
  • …wait…
  • …wait…
  • request complete, user logged in
  • .then runs
  • dispatch our action
export const loginUser = (email, pwd) => {return (dispatch) => {firebase.auth().signInWithEmailAndPassword(email, pwd).then((user) => {dispatch({type: 'login_user',payload: user,});});};}

So, this is the example of the asynchronous action creator. Inside the then, when the dispatch is called, it then only goes to all the reducers. So, this is how redux thunk helps in making the action creator asynchronous

Now, we have to plug in our AuthReducer so that it can handle the type: 'login_user'

in src/reducers/AuthReducer.js

import {EMAIL_CHANGED,PASSWORD_CHANGED,LOGIN_USER,} from '../actions/types';const INITIAL_STATE = { email: '', password: '', user: null };export default (state = INITIAL_STATE, action) => {console.log(action);switch (action.type) {case EMAIL_CHANGED:return { ...state, email: action.payload };case PASSWORD_CHANGED:return { ...state, password: action.payload };case LOGIN_USER:return { ...state, user: action.payload };default:return state;}}

Also, earlier in our Auth app that we built in the previous part, we implemented the sign up workflow with the sign in flow. So, when the user fails to sign in, firebase then attempts to do a sign up. So, we will do the same. And both the sign in and sign up fails, we will dispatch a failure case to show a red error Authetication Failed! in our LoginForm.js

That’s a good point where we can look at a branch to do some inspection of the code. Branch — 03_Init

The only thing left here is the Spinner part. So, let’s finish it up and then implement navigation in our app. Since, most of the things done so far in this app, are exactly what we have implemented already. Navigation is something totally new, so will discuss that in detail.

To implement a spinner, we have to immediately dispatch an action from the loginUser telling to reducer to update the state for a loading to be true. Once that is done, in our LoginForm component, we can check to see if a spinner should be rendered or the Login Button. This is exactly the same as how we did in the auth app in part 2. Please check Branch — 04_Init for more details.

Navigating users around

In our manager app, we needed to create three major screens. And in react native router, we define a distinguished screen as a scene. So, we have three scenes.

A scene can take many attributes, e.g., key, component, title, initial, etc. We will discuss these four as these are gonna be majorly used in this app.

Attributes —

  • key — key is useful for navigation around from one scene to another scene
  • component — as the name itself suggest, when this scene s about to be rendered, which component it should render actually
  • title — used to show for the header in a scene
  • initial — if given, it means this is the first scene that the mobile should render

We will be using react-native-router-flux in our app for navigation.

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

in src/Router.js

import React from 'react';import { Router, Scene } from 'react-native-router-flux';import LoginForm from './components/LoginForm';const RouterComponent = () => {return (<Router><Scenekey="root"><Scenekey="login"title="Please Login"component={LoginForm}initial/></Scene></Router>)}export default RouterComponent;

in src/App.js

...
return (
<Provider store={store}><Router /></Provider>);
...

And you should see the Login page rendered correctly

Now, it’s time to implement the PlayersList component and render the screen when the user is logged in successfully.

in src/components/PlayersList.js

import React, { Component } from 'react';import { View, Text } from 'react-native';class PlayersList extends Component {render() {return (<View><Text>Players List</Text><Text>Players List</Text><Text>Players List</Text><Text>Players List</Text><Text>Players List</Text><Text>Players List</Text></View>);}}export default PlayersList;

In src/Router.js

...
<Router>
<Scenekey="root"><Scenekey="login"title="Please Login"component={LoginForm}initial/><Scenekey="playersList"title="Players"component={PlayersList}/></Scene></Router>...

Do note the key for PlayersList component — playersList

And we need to go to this component when LOGIN_USER_SUCCESS is dispatched—

import { Actions } from 'react-native-router-flux';
...
dispatch({
type: LOGIN_USER_SUCCESS,payload: user,});
Actions.playersList(); // same as the key used for PlayersList component
...

As we have seen already that our app has three major screens — LoginForm, PlayerList and CreatePlayer. It can be seen in the demonstration that when a user logs in successfully, it sees the PlayersList screen and a back button appears at the top automatically which when clicked takes the user back to the LoginForm. This is something that we don’t want in our app. To avoid this behaviour, we group the scenes into buckets.

<Scenekey="auth"><Scenekey="login"title="Please Login"component={LoginForm}initial/></Scene><Scenekey="main"><Scenekey="playersList"title="Players"component={PlayersList}/></Scene><Scenekey="createPlayer"title="Create Player"component={CreatePlayer}/></Scene></Router>

As you can see above, that the Login is put inside the auth bucket and playersList and createPlayer are put together inside the main bucket, so when we navigate from Login to playersList, it will not show the back button. It will only show the back (or) the forward button when navigation for the scenes in the same bucket. And we have to change our navigation to when the user is successfully logged in —

Actions.main(); // same as the key used for PlayersList

As per the PlayersList screen, we need to add a “Add” button on the right and hook it to CreatePlayer screen. Therefore, in src/components/CreatePlayer.js

import React, { Component } from 'react';import { View, Text } from 'react-native';class CreatePlayer extends Component {render() {return (<View><Text>Player Form</Text></View>);}}export default CreatePlayer;

in src/Router.js

...
<Scene
rightTitle="Add"onRight={() => Actions.createPlayer()}key="playersList"title="Players"component={PlayersList}initial/><Scenekey="createPlayer"title="Create Player"component={CreatePlayer}/></Scene>...

So, now it’s time to implement the CreatePlayer.js component

So, this component will have two inputs for name and phone. And then we will have a picker to choose from batsman, bowler, or all-rounder. And a save button for now. So, let’s start that —

To connect the name, phone and skills field with redux, we won’t write it for each of them like we did for email and password in auth. But instead we will write a single action that will work for all of them.

in src/actions/PlayerActions.js

export const updatePlayer = ({ prop, value }) => {return {type: PLAYER_UPDATE,payload: { prop, value },};}

in src/reducers/PlayerReducer.js

import { PLAYER_UPDATE } from '../actions/types';const INITIAL_STATE = {name: '',phone: '',skill: '',};export default (state = INITIAL_STATE, action) => {switch (action.type) {case PLAYER_UPDATE:return { ...state, [action.payload.prop]: action.payload.value }; // our payload from PlayerActions { prop, value }. [action.payload.prop] is key interpolation and it determined at runtimedefault:return state;}}

in src/components/CreatePlayer.js

...
updatePlayerField = ({ prop, value }) => {
this.props.updatePlayer({ prop, value });}......
<Input
label="Name"placeholder="Sachin"onChangeText={(value) => this.updatePlayerField({ prop: "name", value })}value={this.props.name}...

And the connect from react-redux will allow to use this props inside the component. Will put up a branch once we complete the picker component for the createPlayer component. However, do note it is much the same as what we did for LoginForm component.

The next thing to implement is a Picker for the skill in the createPlayer page. For that we will using a Picker component from react native.

In src/components/CreatePlayer.js

...
<Picker
selectedValue={this.props.skill}style={{ flex: 1 }}onValueChange={(value) => this.updatePlayerField({ prop: "skill", value })}><Picker.Item label="Batsman" value="Batsman" /><Picker.Item label="Bowler" value="Bowler" /><Picker.Item label="AllRounder" value="AllRounder" /></Picker>...

Branch — 05_Init

So, this is pretty much what we will discuss in this part. We brushed up on the concepts that we learned before in react-native to build a somewhat larger app from scratch. And learned about how we can achieve navigation in a mobile app using react-native

  • Larger app
  • How components fit together
  • Navigation
  • Picker Component

See ya in next part :)

Part 1 — https://medium.com/@anujbaranwal/react-native-and-redux-part-1-754c13a440ba

Part 2 — https://medium.com/@anujbaranwal/react-native-and-redux-part-2-14c108a65390

Part 3 — https://medium.com/@anujbaranwal/react-native-and-redux-part-3-cbd61e6b303

Part 4 — https://medium.com/@anujbaranwal/react-native-and-redux-part-4-be182ad3fc10

Part 5 — https://blog.usejournal.com/react-native-and-redux-part-5-3185f8f0609b

Part 6 — Final Part — https://medium.com/@anujbaranwal/react-native-and-redux-the-final-part-d97a6269a4bd

More where this came from

This story is published in Noteworthy, where thousands come every day to learn about the people & ideas shaping the products we love.

Follow our publication to see more stories featured by the Journal team.

--

--

Anuj Baranwal

Full Stack Web Developer @AdobeSystems Bangalore. Sometimes I code, I draw, I play, I edit and write