Sie sind auf Seite 1von 164

Table

of Contents
Introduction 1.1
React Native Internals 1.2
Setting up the project 1.3

Start building the app


See it in action! 2.1
Project Structure 2.2
Customizing the project structure 2.2.1
Creating basic components and writing platform-specific code 2.2.2
Conventions and Code Style 2.3
ESLint: The guardian of code conventions ⚔ 2.3.1
Github Pre-push/Pre-commit Hooks 2.3.2
Environment Variables 2.3.3
Speed up development with some and ES7 features 2.3.4
Testing and Debugging 2.4
Jest setup 2.4.1
Snapshots 2.4.2
Testing stateful components using Enzyme 2.4.3
Mocking RN modules 2.4.4
Styling 2.5
Theme Variables 2.5.1
Common Styles/Mixins 2.5.2
Separating styles from component 2.5.3
Redux 2.6
Redux setup 2.6.1
Presentational VS Containers 2.6.2
Navigation 2.7
Using React-navigation 2.7.1
Integrating with redux store 2.7.2

2
File Structure for routes 2.7.3
DevOps ⚙ 2.8
Android Build setup 2.8.1
iOS Build setup 2.8.2
SVG Icons using react-native-vector-icons 2.9
Custom Icon set 2.9.1
Internationalization 2.10
Adding language toggle feature 2.10.1
Integration with react-navigation 2.10.2
Custom Native Modules 2.11
Android Native Modules 2.11.1
iOS Native Modules 2.11.2
References 3.1
The End 3.2

3
Introduction

https://www.reactnative.guide

React Made Native Easy


Written by Rahul Gaba and Atul R

4
Introduction

A reference for building production-grade applications which are easy to test, maintain
and extend to multiple platforms. This book is for the Web developers who have already
got their hands dirty with React and ES6 and want to build complex native apps.

You will learn


How React Native works internally and how to debug RN apps.
How to test and write modular code in react-native.
Redux: the state container.
How to set up a good DevOps pipeline which will increase your team's productivity and
ensure seamless testing.
How to extend your react-native codebase to support web or any other platform by just
following some code conventions.

We are following a native-first approach while keeping an eye out for potentially extending to
the web. You will eventually see how easy it is port the application to the web by following
conventions.

Our knowledge is based on our experience of working with React Native apps for around 2
years and helping clients launch their apps quicker than ever before.

This book is for


React developers who are planning to start with react-native applications.
Web Developers who know basic ReactJS fundamentals and want to learn best
practices for state management and native development.
Native iOS/Android developers who know ReactJS and want to start building apps using
React-Native.
React-Native developers who want to extend their codebase to support other platforms
by just following some code conventions.

We will build a Note Taker application while learning the concepts. There is a link to
the code at the end of every chapter. You can also see the app live if you have the
Expo app on your phone.

So be ready to get your hands dirty.

5
Introduction

Authors

Github link: https://github.com/react-made-native-easy/book

Please star the repo if you like it ;)

DOWNLOAD YOUR COPY


Download a .pdf, .epub, or .mobi here. If you prefer a hard copy, please feel free to take a
printout.

https://www.gitbook.com/book/react-made-native-easy/react-made-native-easy/details

CONTRIBUTIONS
This is an open source book hosted on Github. We will keep updating the contents of the
book as and when it gets outdated. Please feel free to contribute or leave a comment in the
Disqus.

HALL OF THANKS
Reviewer / Proof reading

6
Introduction

Kakul Gupta

Contributors
Will Bowlin
Vishal Vasnani
Aritra Ghosh

GET, SET, CODE!!

7
React Native Internals

React Native Internals


React Native is a framework which allows developers to build native apps using Javascript.
Wait! Cordova already does that and it has been around for quite a while. Why would
anyone want to use RN?

The primary difference between RN and Cordova based apps is that Cordova based apps
run inside a webview while RN apps render using native views. RN apps have direct access
to all the Native APIs and views offered by the underlying mobile OS. Thus, RN apps have
the same feel and performance as that of a native application.

At first, it is easy to assume that React Native might be compiling JS code into respective
native code directly. But this would be really hard to achieve since Java and Objective C are
strongly typed languages while Javascript is not! Instead, RN does something much more
clever. Essentially, React Native can be considered as a set of React components, where
each component represents the corresponding native views and components. For example,
a native TextInput will have a corresponding RN component which can be directly imported
into the JS code and used like any other React component. Hence, the developer will be
writing the code just like for any other React web app but the output will be a native
application.

Ok! This looks like black magic .

To understand this, let us take a look at the architecture and how React Native works
internally.

Architecture
Both iOS and Android have a similar architecture with subtle differences.

If we consider the big picture, there are three parts to the RN platform:

1. Native Code/Modules: Most of the native code in case of iOS is written in Objective C
or Swift, while in the case of Android it is written in Java. But for writing our React Native
app, we would hardly ever need to write native code for iOS or Android.

2. Javascript VM: The JS Virtual Machine that runs all our JavaScript code. On
iOS/Android simulators and devices React Native uses JavaScriptCore, which is the
JavaScript engine that powers Safari. JavaScriptCore is an open source JavaScript
engine originally built for WebKit. In case of iOS, React Native uses the JavaScriptCore

8
React Native Internals

provided by the iOS platform. It was first introduced in iOS 7 along with OS X
Mavericks.
https://developer.apple.com/reference/javascriptcore.

In case of Android, React Native bundles the JavaScriptCore along with the application.
This increases the app size. Hence the Hello World application of RN would take
around 3 to 4 megabytes for Android.

In case of Chrome debugging mode, the JavaScript code runs within Chrome itself
(instead of the JavaScriptCore on the device) and communicates with native code via
WebSocket. Here, it will use the V8 engine. This allows us to see a lot of information on
the Chrome debugging tools like network requests, console logs, etc.

3. React Native Bridge: React Native bridge is a C++/Java bridge which is responsible for
communication between the native and Javascript thread. A custom protocol is used for
message passing.

In most cases, a developer would write the entire React Native application in Javascript. To
run the application one of the following commands are issued via the CLI - react-native
run-ios or react-native run-android . At this point, React Native CLI would spawn a node

packager/bundler that would bundle the JS code into a single main.bundle.js file. The
packager can be considered as being similar to Webpack. Now, whenever the React Native
app is launched, the first item to be loaded is the native entry point. The Native thread
spawns the JS VM thread which runs the bundled JS code. The JS code has all the
business logic of the application. The Native thread now sends messages via the RN Bridge
to start the JS application. Now, the spawned Javascript thread starts issuing instructions to
the native thread via the RN Bridge. The instructions include what views to load, what
information is to be retrieved from the hardware, etc. For example, if the JS thread wants a
view and text to be created it will batch the request into a single message and send it across
to the Native thread to render them.
[ [2,3,[2,'Text',{...}]] [2,3,[3,'View',{...}]] ]

The native thread will perform these operations and send the result back to the JS assuring
that the operations have been performed.

9
React Native Internals

Note: To see the bridge messages on the console, just put the following snippet onto the
index.<platform>.js file

import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue';


MessageQueue.spy(true);

Threading Model
When a React Native application is launched, it spawns up the following threading queues.

1. Main thread (Native Queue) - This is the main thread which gets spawned as soon as
the application launches. It loads the app and starts the JS thread to execute the
Javascript code. The native thread also listens to the UI events like 'press', 'touch', etc.
These events are then passed to the JS thread via the RN Bridge. Once the Javascript
loads, the JS thread sends the information on what needs to be rendered onto the
screen. This information is used by a shadow node thread to compute the layouts. The
shadow thread is basically like a mathematical engine which finally decides on how to
compute the view positions. These instructions are then passed back to the main thread
to render the view.

2. Javascript thread (JS Queue) - The Javascript Queue is the thread queue where main
bundled JS thread runs. The JS thread runs all the business logic, i.e., the code we
write in React Native.

3. Custom Native Modules - Apart from the threads spawned by React Native, we can
also spawn threads on the custom native modules we build to speed up the
performance of the application. For example - Animations are handled in React Native
by a separate native thread to offload the work from the JS thread.

Links: https://www.youtube.com/watch?v=0MlT74erp60

10
React Native Internals

React.js Conf 2016 - Tadeu Zagallo - Optimising…


14K views • No comments

Your browser does not currently recognize any of the video formats
available.
Click here to visit our frequently asked questions about HTML5 video.

0:00 / 24:13

View Managers
View Manager is a native module that maps JSX Views onto Native Views. For example:

import React, { Component } from 'react';


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

class HelloWorldApp extends Component {


render() {
return (
<View style={{padding:40}}>
<Text>Hello world!</Text>
</View>
);
}
}

export default HelloWorldApp;


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

11
React Native Internals

Here when we write <Text /> , the Text View manager will invoke new
TextView(getContext()) in case of Android.

View Managers are basically classes extended from ViewManager class in Android and
subclasses of RCTViewManager in iOS.

Development mode
When the app is run in DEV mode, the Javascript thread is spawned on the development
machine. Even though the JS code is running on a more powerful machine as compared to
a phone, you will soon notice that the performance is considerably lower as compared to
when running in bundled mode or production mode. This is unavoidable because a lot more
work is done in DEV mode at runtime to provide good warnings and error messages, such
as validating propTypes and various other assertions. Furthermore, the latency of
communication between the device and the JS thread also comes into play.

12
React Native Internals

Link: https://www.youtube.com/watch?v=8N4f4h6SThc - RN android architecture

Under The Hood of React Native | Martin Konicek | Reac…

Your browser does not currently recognize any of the video formats
available.
Click here to visit our frequently asked questions about HTML5 video.

0:00 / 30:36

13
Setting up the project

Setting up the Project


This section covers the basic boilerplate setup of a sample project that we will be using to
learn React Native. We believe that one should set up his/her project from scratch or use a
minimal boilerplate instead of using a ready-made, feature rich boilerplate. The reason
behind this is that when a developer/team sets up its own boilerplate, they know exactly
what is going into the app. A lot of boilerplates have features/libraries which the team will be
unaware of and it would only contribute to code clutter.

Popular boilerplates' review


create-react-native-app OR
Expo OR
react-native init

React-native has a bunch of options to set up a project. create-react-native-app , react-


native init and Expo are among the most popular ones and interestingly, all three of them

are mentioned on the official website of react-native. It might create a lot of confusion for
someone who is starting with RN to decide which one to use.

Here is a short description of each of them:

create-react-native-app: It is similar to create-react-app . It has all the necessary


tasks to run your application. All the necessary setup has been taken care of and you
can start hacking around react-native. This is very useful if you are starting with react-
native and don't want to worry about anything else. It uses Exponent's open source
tools to debug the application. To run the app, all you need to do is install the Expo
Client app and scan a QR code. Although it is very quick to setup, everything seems like
a black-box. Due to this, it can get pretty messy when you want to change a lot of
configurations. Hence we do not recommend it for a long-term production application.

Expo: It is a third-party framework which provides you with a lot of cool features like
sharing your app with anyone while you are developing it and showing live code
changes on a real device by just scanning a QR code. We do not recommend using this
if your app uses a lot of third party native modules or you wish to hack around the native
code since you don't get access to native code out of the box. Refer to this page to
know more: Why not Expo? Also, it supports only Android 4.4 and above, which can
be a big turn off if your user base has a large number of Android 4.1 - 4.3 users.

14
Setting up the project

react-native init: This provides you with a very basic setup of the application including
native ios and android folders. This allows you to change the native code if required.
You would use native Android/iOS simulators or devices to run your application. You
can run the dev version of the application with react-native run-ios (it will open the
iOS simulator and run your app on it). Here we will need to setup everything from
scratch. But on the contrary, we get full access to native code and have control over
almost everything that is going on in our application. Also, it is easier to upgrade react-
native and other core libraries using this boilerplate as compared to others. Hence any

critical updates can be integrated much more easily. Thus, we recommend this
boilerplate for any long term production application.

Setup instructions for react-native init can be found at https://facebook.github.io/react-


native/docs/getting-started.html. You can find them at the Building Projects with
Native Code section.

15
See it in action!

Your final app will look like this

16
Project Structure

Project structure
We will be creating a note-taking App in React Native. Let's call it NoteTaker . By the end of
this book, we will be able to build and deploy the NoteTaker to the Android Play Store and
the Apple App Store for users to download. We will begin with the standard React Native
boilerplate and as we progress through the concepts we will keep updating it. Also, at the
end of the book, we will show how to extend our app originally written in Android and iOS to
any other platforms, for example, web (because we just love web). So, let us begin.

Boilerplate
To create a new project run react-native init <project-name> . Example: react-native init
notetaker . We will use this as our base project in this book. Once the project setup is

complete you should have a project structure similar to this.

.
├── .babelrc
├── .buckconfig
├── .flowconfig
├── .gitattributes
├── .gitignore
├── .watchmanconfig
├── android
├── ios
├── node_modules
├── __tests__
│ ├── index.android.js
│ └── index.ios.js
├── app.json
├── index.android.js
├── index.ios.js
├── package.json
└── yarn.lock

Run the app


First, let's run the project to see how it looks.

Type the command

react-native run-ios - for running the app on iOS simulator

or

17
Project Structure

react-native run-android - for running the app on a connected Android phone/emulator.

Note that for react-native run-android to work, you should have an open Android emulator
or an Android device with USB Debugging enabled connected to your system via a USB
cable.

If all goes well you should see the following screen on your iOS or Android emulator.

If you noticed there are two entry point files index.ios.js and index.android.js . Hence,
when we run the command react-native run-ios , the file index.ios.js serves as our
entry point.

Note: The above is applicable only to React Native 0.49 and below. Later versions
have only one entry point index.js

Under the hood, when we run the command react-native run-ios , the iOS native project
inside ios directory starts compiling. Along with the native project, react-native packager
kicks in on another terminal window and runs on port 8081 by default. The packager is

18
Project Structure

responsible for the compilation of JavaScript code to a js bundle file. When the native project
is successfully launched on the simulator, it asks the packager for the bundle file to load.
Then all the instructions inside the js code are run to successfully launch the app.

The code till here can be found on the branch chapter/5/5.0

19
Customizing the project structure

Begin customizing the project


Let's begin by creating a directory named app in the project folder. The app folder will have
all our JavaScript source code.

Create a file index.js at app/index.js . This file will serve as the common entry point for
both Android and iOS projects.

Modify the files as follows:

Note: In the newer versions of react-native, there is only one index file for both Android and
iOS called index.js. Hence if you are using react-native 0.50+ then change only one file.

index.ios.js

import {AppRegistry} from 'react-native';


import app from './app/index';

AppRegistry.registerComponent('NoteTaker', () => app);


export default app;

index.android.js

import {AppRegistry} from 'react-native';


import app from './app/index';

AppRegistry.registerComponent('NoteTaker', () => app);


export default app;

app/index.js

20
Customizing the project structure

/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/

import React, { Component } from 'react';


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

class NoteTaker extends Component {


render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
</View>
);
}
}

const styles = StyleSheet.create({


container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

export default NoteTaker;

As you can see, now we have a single entry point to the code base. Hence both react-
native run-android and react-native run-ios will eventually run the same code
app/index.js

If you run the code now in iOS simulator, you should see

21
Customizing the project structure

Directory structure
Now, let's create a few directories inside /app that will help us structure our code such that
it is modular and easier to maintain.

cd app
mkdir assets components config pages redux
mkdir routes styles utils

So your project directory should now look like this

22
Customizing the project structure

.
├── __tests__
│ ├── index.android.js
│ └── index.ios.js
├── app
│ ├── assets
│ ├── config
│ ├── styles
│ ├── utils
│ ├── components
│ ├── pages
│ ├── routes
│ ├── redux
│ └── index.js
├── app.json
├── index.android.js
├── index.ios.js
├── package.json
└── yarn.lock

app/assets - This is where all the images, videos, etc will go in.

app/config - This is where configurations for the app will go in. For example, your

environment specific config for dev and prod, etc.


app/styles - This is where your global styles, themes, and mixins will go.

app/utils - This is where all the services/utility files such as HTTP utility to make API

calls, storage utility, data transformation utility, etc will go.


app/components - The directory will contain all the dumb components. In short, these

components will only do layouting and won't contain any states or business logic inside
them. All the data to these components will be passed in as props. This concept will be
explained in detail further in the book.
app/pages - This directory will hold all the smart components. Smart components are

those components which contain business logic and states in them. Their job is to pass
the props to the dumb components after all the business logic has been executed.
app/routes - This is where we will keep all our app's routing logic. This will contain the

map between the pages(smart components) and the routes.


app/redux - This will contain all our redux state management files like actions,

reducers, store config, thunks etc.

Helper npm scripts


Before we continue any further, let us add two helper scripts to our package.json

Modify the package.json scripts to look like this

23
Customizing the project structure

{
.....
.....
.....
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest",
"ios": "react-native run-ios",
"android": "cd android && ./gradlew clean && cd .. && react-native run-android
"
},
.....
.....
.....
}

From now on we will type yarn ios or yarn run ios from the command line to run the app
in iOS simulator. Similarly, for Android, we will type yarn android or yarn run android . In
case of Android, we are doing an additional step ./gradlew clean . This allows us to clear
any build cache that Android created before making an apk. This solves a lot of cache
related issues in case of Android during development.

24
Customizing the project structure

The code till here can be found on the branch chapter/5/5.1

25
Creating basic components and writing platform-specific code

Creating basic components


Since we have a rough idea of what goes where from the previous page, let us create our
first basic UI component. We will create a simple text area where we can enter our note text.
React Native already bundles many basic UI components such as Text, View, TextInput, etc
into the package react-native .

TextArea component
Create two files

TextArea UI component - app/components/TextArea/TextArea.component.js

import React, { Component } from 'react';


import { TextInput, View } from 'react-native';
import PropTypes from 'prop-types';
import styles from './TextArea.component.style';

class TextArea extends Component {


state = {
text : ''
}
render() {
const {...extraProps} = this.props;
return (
<TextInput
{...extraProps}
style={[styles.textArea, extraProps.style]}
multiline = {true}
onChangeText={(text) => this.setState({text})}
value={this.state.text}
/>
);
}
}

export default TextArea;

TextArea style file - app/components/TextArea/TextArea.component.style.js

26
Creating basic components and writing platform-specific code

import {StyleSheet} from 'react-native';

export default StyleSheet.create({


textArea: {
width: 200,
height:100,
borderColor: 'gray',
borderWidth: 1
}
});

This will be our TextArea UI component that we will use to enter our text.

Also, let's add our UI screen which will contain the TextArea component.

Home screen component - app/components/Home/Home.component.js

import React, { Component } from 'react';


import { TextInput, View ,Text} from 'react-native';
import PropTypes from 'prop-types';
import styles from './Home.component.style';
import TextArea from '../TextArea/TextArea.component';

class Home extends Component {


render() {
return (
<View style={styles.container}>
<Text> Please enter your note here</Text>
<TextArea />
</View>
);
}
}

export default Home;

Home screen style file - app/components/Home/Home.component.style.js

import {StyleSheet} from 'react-native';

export default StyleSheet.create({


container: {
flex:1,
padding: 20,
alignItems: 'center'
}
});

27
Creating basic components and writing platform-specific code

And finally let's add the Home screen to the main app.

Let's modify the app/index.js to include our home screen.

app/index.js

/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import Home from './components/Home/Home.component';

class NoteTaker extends Component {


render() {
return (
<Home />
);
}
}

export default NoteTaker;

If you run the app now, you should have something that looks like this

28
Creating basic components and writing platform-specific code

Platform-specific code
Now, as we can see we have different outputs in Android and iOS.

To solve this we create an extra file TextArea.component.android.js with the content

29
Creating basic components and writing platform-specific code

import React, { Component } from 'react';


import { TextInput, View } from 'react-native';
import PropTypes from 'prop-types';
import styles from './TextArea.component.style';

class TextArea extends Component {


state = {
text : ''
}
render() {
const {...extraProps} = this.props;
const alignTextTop = {textAlignVertical: 'top'};
return (
<TextInput
{...extraProps}
style={[styles.textArea, alignTextTop, extraProps.style]}
multiline = {true}
onChangeText={(text) => this.setState({text})}
value={this.state.text}
underlineColorAndroid={'transparent'}
/>
);
}
}

export default TextArea;

Reload the app.

You should see that both Android and iOS TextAreas look the same now.

30
Creating basic components and writing platform-specific code

Basically, under the hood, while importing/requiring a file during bundling the js, say
TextArea.component , using the syntax

import TextArea from './TextArea.component'


or
var TextArea = require('./TextArea.component')

,the Android React Native packager looks for the files in the following order

TextArea.component.android.js and then TextArea.component.js

Similarly, the order in case of iOS is

TextArea.component.ios.js and TextArea.component.js

Note: Platform specific code can also be written by making use of the Platform module
from react-native.

For example, we can also modify TextArea.component.js into

31
Creating basic components and writing platform-specific code

import React, { Component } from 'react';


import { TextInput, View } from 'react-native';
import PropTypes from 'prop-types';
import styles from './TextArea.component.style';
import {Platform} from 'react-native';

class TextArea extends Component {


state = {
text : ''
}
render() {
const {...extraProps} = this.props;
const alignTextTop = Platform.OS ==='android' ? {textAlignVertical: 'top'}: {};
return (
<TextInput
{...extraProps}
style={[styles.textArea, alignTextTop, extraProps.style]}
multiline = {true}
onChangeText={(text) => this.setState({text})}
value={this.state.text}
underlineColorAndroid={'transparent'}
/>
);
}
}

export default TextArea;

Notice that here only a single TextArea.component.js file is present for both Android and
iOS. This will also produce similar results.

But we prefer to use the first method where we create separate files with .android.js and
.ios.js extensions.

1. In the case of different extensions we have different files, hence only the code needed
for running our logic is bundled into the application. For example, let's assume you
decide that you will use a custom library for TextArea only for Android. Now if we import
that library into TextArea.android.js , only in the case of the Android bundling will a new
library be added, iOS will remain the same. Hence, it reduces our code size when
bundling.

2. In practice, we have noticed that Platform module method is error prone as we have to
manually check for Platform.OS wherever there are subtle differences in the output.

32
Creating basic components and writing platform-specific code

The code till here can be found on the branch chapter/5/5.2

33
Conventions and Code Style

Conventions
Every team is composed of developers who follow different conventions. Hence, we often
find that code written by a developer is not easily understood by another developer on the
same team. Not having a proper convention creates dependencies on individuals and
makes the project difficult to understand by a newcomer. Tight dependencies in a
software development project can affect the velocity of the team.

The best way to solve this is by deciding and following code conventions.

We strongly believe that NO convention is bad. As long as there is some convention


and it is followed religiously, it is good.

Code conventions can be as easy as following 4 spaces instead of tabs, or always ending a
statement with a semicolon, etc. It could also be something more complex like not allowing
setState() to be invoked on componentWillMount of a React Component.

So if you want your team to code like this guy, set and follow conventions.

34
Conventions and Code Style

35
ESLint: The guardian of code conventions ⚔

ESLint: The guardian of code conventions



ESLint is a tool that allows us to maintain code quality and enforce code conventions.
ESLint is a static code evaluator. Basically, it means that ESLint will not actually execute the
code but will instead read through the source code to see if all the preconfigured code
conventions are followed by the developers.

ESLint allows us to maintain consistent code style throughout the project. Thus, any
developer on the team can easily understand the code written by another developer.
This can exponentially increase the team's velocity and avoid dependencies. It can
reduce human errors and can act as a guardian that maintains code conventions.

Examples
Apart from code conventions, ESLint also spots common mistakes made by developers. For
example,

var a = 1, b = 2, c = 3, e = 4;

var test = function() {


console.log(a, b, c, d, e);
};

The above code will compile fine. But as soon as you execute it, it will throw a runtime
exception ReferenceError

test()

VM206:4 Uncaught ReferenceError: d is not defined


at test (<anonymous>:4:22)
at <anonymous>:1:1
test @ VM206:4
(anonymous) @ VM222:1

These mistakes can be easily detected by ESLint.

Installation
It is pretty easy to setup ESLint for a project.

36
ESLint: The guardian of code conventions ⚔

yarn add --dev eslint babel-eslint eslint eslint-plugin-react eslint-plugin-react-nati


ve

This would install ESLint and other useful plugins as your dev dependencies.

After this, add an npm script in your package.json as given below.

Your package.json should now have

{
"name": "testapp",
"version": "0.0.1",
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
....
....
....
"lint": "eslint app/",
"lint:fix": "eslint app/ --fix"
},
"dependencies": {
....
....

You can simply run

npm run lint


or
npm run lint:fix

npm run lint will run the ESLint and show a list of errors that need to be fixed. npm run

lint:fix will run ESLint and attempt to correct the errors it is able to fix automatically.

The icing on the cake


Most modern editors have support for ESLint via plugins. The benefit of a text editor ESLint
plugin is that these plugins suggest corrections while we write code, thus saving a lot of time
for developers.

An editor configured with ESLint would look something like this.

37
ESLint: The guardian of code conventions ⚔

Some of these plugins also support features like lint on save. Thus, ESLint attempts to run
eslint --fix <current_file> the moment you save (cmd+s) a file. This fixes all auto-

fixable lint errors such as incorrect indentation spaces, adding semicolons at the end of a
line, etc.

We strongly recommend enabling this feature.

The .eslintrc file


ESLint rules can be configured via a configuration file .eslintrc which should be placed in
the root directory of the project.

A sample .eslintrc file looks like this:

38
ESLint: The guardian of code conventions ⚔

# "off" or 0 - turn the rule off


# "warn" or 1 - turn the rule on as a warning(doesn’ t affect exit code)
# "error" or 2 - turn the rule on as an error(exit code is 1 when triggered)
{
"parser": "babel-eslint",
"env": {
"browser": true
},
"plugins": [
"react",
"react-native"
],
"ecmaFeatures": {
"jsx": true
},
"extends": ["eslint:recommended", "plugin:react/recommended"],
"rules": {
"react/no-did-mount-set-state": 2,
"react/no-direct-mutation-state": 2,
"react/jsx-uses-vars": 2,
"no-undef": 2,
"semi": 2,
"react/prop-types": 2,
"react/jsx-no-bind": 2,
"react/jsx-no-duplicate-props": 2,
....
....
....
},
"globals": {
"GLOBAL": false,
"it": false,
"expect": false,
"describe": false,
....
....
....
}
}

The important area in the above configuration is the rules section. This section controls all
the code conventions followed in the project.

The complete list of all the available rules is present here: http://eslint.org/docs/rules/

It can be pretty overwhelming at first to decide which rules should go in. Hence we can start
with

"extends": ["eslint:recommended", "plugin:react/recommended"]

39
ESLint: The guardian of code conventions ⚔

These should cover all the basic rules needed to start your project.

But we recommend adding these as well

"rules": {
"react/no-did-mount-set-state": 2,
"react/no-direct-mutation-state": 2,
"react/jsx-uses-vars": 2,
"no-undef": 2,
"semi": 2,
"react/prop-types": 2,
"react/jsx-no-bind": 2,
"react/jsx-no-duplicate-props": 2,
....
....
....
},

Rest of the rules can be added based on what conventions the team decides to follow.

Recommendations
We would suggest adding more of auto-fixable rules as the corrections suggested by these
rules can be fixed by the editor with an ESLint plugin on file save. This would reduce the
time that a developer would spend fixing lint errors than writing actual code.

We also recommend using ESLint for spacing/tabs instead of other methods like
.editorconfig . This way, all the code conventions can be configured via a single utility

(eslint) and the fixes can be made by the editor itself on save.

The code till here can be found on the branch chapter/6/6.1

40
Github Pre-push/Pre-commit Hooks

Git Pre-push/Pre-commit Hooks


Pre-push/Pre-commit hooks are nothing but commands which you would want to run
every time you push/commit something. It might not sound very interesting, but using
them with Jest and ESLint can protect the quality of your code at a much, much higher
level.

"How?" you ask

Pre hooks run a shell command which you specify and if the command fails with exit status
1, the process will get terminated. We store the shell commands inside the .git/hooks
directory of the root of the project. So imagine that you won't be able to push the code if
your tests are failing or your js files are not properly linted. That sounds interesting
right?

Your next question might be, how do you make sure that everyone adds those
commands to the pre-push hooks folder?

Don't worry, we have that covered as well. Let me introduce you to an awesome NPM
module called husky .

Using Husky

41
Github Pre-push/Pre-commit Hooks

Just run npm install --save-dev husky or yarn add --dev husky

After installing these node-modules, all you need to do is specify which command you would
like to execute before pushing/committing your code. And this can be specified within
package.json . It will make sure to add the commands to your local git hooks folder while

setting up (npm install) the machine.

We used husky's prepush hook to make sure that every line of code which is being pushed
is linted and all the tests pass.

If you look at the package.json file of our boilerplate, you would find this snippet:

{
"scripts": {
"prepush": "npm run lint && npm run test",
"postinstall": "rm -rf .git/hooks/pre-push && node node_modules/husky/bin/install.
js && rm -rf .git/hooks/pre-commit"
},
"devDependencies": {
"husky": "^0.14.3"
}
}

As you can see, we are doing npm run lint and npm run test every time the developer
tries to push something. If any of the commands fail, the developer would not be allowed to
push, greatly reducing the chances of build failure/PR check failure in CIs.

Internally, these modules write in .git/hooks folder in pre-commit/pre-push files. After


adding these modules and doing npm install, you will find something like this in
.git/hooks/pre-push :

42
Github Pre-push/Pre-commit Hooks

#!/bin/sh
#husky 0.14.3

command_exists () {
command -v "$1" >/dev/null 2>&1
}

has_hook_script () {
[ -f package.json ] && cat package.json | grep -q "\"$1\"[[:space:]]*:"
}
...
...

This code is autogenerated by the node module so we don't have to worry about it. If you
are not able to find this file, it means that your hooks were not properly installed. Please
uninstall and install the module again or do npm run postinstall .

Note: In the case of Windows machines, make sure that you have bash(Cygwin).
Prepush hooks will fail if you use Command Prompt

The code till here can be found on the branch chapter/6/6.2

43
Environment Variables

Environment variables
Since react-native combines native platform code with JS, you might find it hard to
accomplish small tasks like passing environment variables. But don't worry, we have you
covered here as well.

How do Environment variables work in react-native?


Since the JS code is being executed by the native platform, we cannot directly pass env
variables like we used to in node/Webpack projects. So we found an alternative way to
accomplish this.

How to use Environment variables in RN?


We created an env.config.js file which exports an object. The env object can contain the
API base URL, environment, and even the app fixtures/mock API data, etc (we will discuss
this in details in API mocks section). And on the CI (Travis in our case), we change the
contents of the file depending on the build type(Dev, UAT or Prod). Sounds cool right?

The app will contain all the env configs inside environment/ folder. The CI will replace the
contents of env.config.js with environment/prod.config.js before starting the build
process.

How to change env config using TravisCI?

44
Environment Variables

The command is pretty simple. All we need to do is copy the contents of one file to another.

cp app/config/env/prod.env.js app/config/env.config.js

Head over to TravisCI chapter to know how and where to put the above-mentioned
command.

The code till here can be found on the branch chapter/6/6.3

45
Speed up development with some and ES7 features

Speed up development with some ES7


features.
We all have got our hands dirty with ES6 already and loved it. React Native supports some
of the ES7 features out of the box so no additional setup is required to use them. Let me
introduce you to some of the ES7 features which will surely help you speed up your
development:

Class properties instead of constructor


We all have used constructor() in our class components, used .bind while calling class
member functions and faced problems accessing this.setState or this.props .
Unfortunately, that's how ES6 classes work. I am not going to explain why we need to do all
this (there are better resources available online to understand that ). But I can tell you a
really interesting ES7 feature which you will love. Let me introduce you to ES7 Class
instances.

By using this feature, you can define class members ( state for eg.) without the need of
constructor .

Before:

class SomeComponent extends Component {


constructor() {
super();
this.state = {
count: 0
}
}
...
}

After:

class SomeComponent extends Component {


state = {
count: 0
}
...
}

46
Speed up development with some and ES7 features

Arrow functions instead of class methods


Using the above feature, we can also define arrow functions as class instances and since
arrow functions do not have their own scope, this inside arrow function will always point to
the class. Therefore you do not need to do binding of this inside constructor. And in most
of the cases, you would not be required to use constructor at all. In our project, we never
used constructor in any of the components

Before:

class SomeComponent extends Component {


_incrementCounter() {
this.setState({count: this.state.count+1})
}
constructor() {
this._incrementCounter = this._incrementCounter.bind(this);
}
...
}

After:

class SomeComponent extends Component {


_incrementCounter = () => {
this.setState({count: this.state.count+1})
}
...
}

Object rest spread


ES6 already supports array spread operator. You can use the same syntax for objects as
well. So instead of writing Object.assign({},a,{b:2}) , we can directly use {...a, b:2} .

You might say that there is nothing new in this. But if used well, it can make your React code
much more beautiful and clean. Let me show you the code before and after using spread
operator.

Before

47
Speed up development with some and ES7 features

class SomeComponent extends Component {


static defaultProps = {
someProp: {}
}
render() {
const someProp = this.props.someProp;
}
}

After

class SomeComponent extends Component {


render() {
const {someProp={}} = this.props;
}
}

48
Testing and Debugging

Testing
With the growing complexities of applications, unit testing becomes mandatory. Since we are
writing code in JS, we can utilize most of the testing frameworks/libraries available for
React/web apps without requiring many changes.

We recommend using the Jest Framework plus some additional utilities like Enzyme to
make a developer's life easier.

What's the use of testing UI code?


We all have asked or been asked this question at least once while working on frontend. And
until recent years, the term unit testing was not very popular among FE developers. Here are
some of the reasons why we should write unit tests for FE code.

It lets you capture bugs before the QA team does:

We all know that it is difficult for a QA and a dev to live in harmony. Devs do not like it
when the QA team finds bugs in their work and tells them to make changes. If your code
has a good test coverage, there are fewer chances of finding bugs. That's a win-win
situation for both sides right?

Helps other devs understand your code better:

All the test frameworks have a describe block where you define what the method is
supposed to do, just how you would write comments for your code.

Refactors your code:

You will start asking these questions to yourself while coding: How will I test this code?,
How do I make sure that each method I write can be tested? If you already ask these
questions while writing code, then you are among a small minority of developers.

Makes your code modular:

If your code is tested, there are 99% chances that it is modular, which means that you
can easily implement changes in the future.

Makes debugging/implementing changes much, much easier:

There will be cases when you will be required to change a function's logic, e.g., a
currency formatter function which returns a string. One way of debugging it would be to
go through the UI and check if the desired output is showing up. Another smart way

49
Testing and Debugging

would be to fire your test cases, change the test results according to your desired output
and let the test fail. Now change the logic inside your method to make the failed test
pass.

Does NOT increase development time:

Many of us give the excuse that writing test cases will increase the development time
(even I was of the same opinion). But believe me, it doesn't. During the course of
development, almost half of the time is spent in debugging/bug fixing. Writing unit tests
can decrease this time from 50% to less than 20%.

50
Jest setup

Jest setup
For all those who have not heard about Jest, have a quick look here:
https://facebook.github.io/jest/

Jest is used by Facebook to test all JavaScript code including React applications. One
of Jest's philosophies is to provide an integrated, "zero-configuration" experience. We
observed that when engineers are provided with ready-to-use tools, they end up writing
more tests, which in turn results in more stable and healthy codebases.

We used Jest because of the following reasons:

Minimal configuration
Watches only changed files
Fast
Snapshot testing (explained later)
Coverage out of box

4 important test scripts every project should have

"scripts": {
"test": "jest --verbose --coverage",
"test:update": "jest --verbose --coverage --updateSnapshot",
"test:watch": "jest --verbose --watch",
"coverage": "jest --verbose --coverage && open ./coverage/lcov-report/index.html",
}

test : It will go through all the test files and execute them. This command will also be

used in pre-hooks and CI checks.


test:watch : This will watch all the test files. It is very useful while writing tests and

quickly seeing results.


test:update : This command will update snapshots for all the presentational

components. If the snapshot is not there, it will create it for you. We will discuss
snapshots in detail in coming chapters.
coverage : As the name suggests, this command will generate a coverage report.

Testing conventions:
It is highly recommended to have conventions for test files as well. Here are the conventions
we follow:

51
Jest setup

Jest recommends having a __test__ folder in the same directory as the file to be
tested.
The naming convention for test files is <testFileName>.test.js . If you are writing tests
for abc.component.js , then the test filename would be abc.component.test.js .
In each expect , we first mention the function name which is to be tested.

Here is a small example following the above mentioned conventions:

//counter.util.js
const counter = (a) => a + 1;

//__test__/counter.util.test.js
describe('counter: Should increment the passed value', () => {
...
});

Jest configuration:
As we read in the documentation, Jest is indeed very easy to set up. You do not need a
separate config file. The configuration is so simple that it can fit inside package.json .

Here is our Jest config:

"jest": {
"preset": "react-native",
"cacheDirectory": "./cache",
"coveragePathIgnorePatterns": [
"./app/utils/vendor"
],
"coverageThreshold": {
"global": {
"statements": 80
}
},
"transformIgnorePatterns": [
"/node_modules/(?!react-native|react-clone-referenced-element|react-navigation)"
]
}

preset : The preset is a node environment that mimics the environment of a React

Native app. Because it doesn't load any DOM or browser APIs, it greatly improves
Jest's startup time.

cacheDirectory : It helps you greatly improve the test speed. It does so by creating a

cache of compiled modules so that it doesn't have to compile the node_modules every
time we run tests.

52
Jest setup

coveragePathIgnorePatterns : Defines the files which we want to skip while generating

coverage reports.

coverageThreshold : Defines the threshold limit for all the tests to pass. If the coverage

is less than the defined limit, the tests would fail. This helped us in keeping code
coverage high throughout development.

transformIgnorePatterns : All the npm modules which need to be transpiled are added

here. These modules are basically ES6/7 modules.

Note: Make sure to add cache and coverage in your gitignore file.

The code till here can be found on the branch chapter/7/7.1

53
Snapshots

Snapshots
This feature makes testing presentational components a lot easier. With a single line, you
can test all your presentational components (their render method only). There is no need to
write test cases for each component returned by render method.

What are Snapshots:


A snapshot is nothing but a configuration file defining your component style, UI, and props.
The test case will look something like this:

__tests__/someComponent.component.test.js

import React from 'react';


import renderer from 'react-test-renderer';
import SomeComponent from '../SomeComponent.component';

describe('Some component', () => {


it('renders correctly', () => {
const tree = renderer.create(
<SomeComponent/>
).toJSON();
expect(tree).toMatchSnapshot();
});
});

Whenever Jest sees this line expect(tree).toMatchSnapshot(); , it is going to generate a


snapshot and compare it with the previously stored snapshot. If the snapshot is not present,
Jest will store the generated snap.

The generated snap file will look something like this:

__tests__/snaphots/someComponent.component.js.snap

54
Snapshots

exports[`SomeComponent Component: SomeComponent renders correctly 1`] = `


<View
style={
Object {
"flex": 1,
}
}
>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Object {
"color": "#000000",
"fontFamily": "Roboto",
"fontSize": 24,
"fontWeight": "500",
"paddingVertical": 20,
"textAlign": "center",
}
}
>
SomeText
</Text>
</View>
`;

As you can see above, the snap contains every possible property of the UI which is being
returned by the render method.

Should I push generated snaps to git?


Yes, you should. Snaps should be there in each dev's machine so that if one of the devs
changes some component unknowingly, the snap test for that component will fail and he/she
would know before pushing it. Even the Jest official documentation says this:

It is expected that all snapshots are part of the code that is run on CI and since new
snapshots automatically pass, they should not pass a test run on a CI system. It is
recommended to always commit all snapshots and to keep them in version control.

What to do when a snap test fails?


Consider this scenario: you worked on a component, generated a snap and pushed it. Later,
another dev named John made some changes in the component. Now the test of the snap
will fail since the snap still contains the code which you wrote. John will just need to update

55
Snapshots

the snapshot to make the test pass. All that is needed to update the test case is one
command: jest --updateSnapshot and he is done.

We recommend creating an npm script for updating snaps. As you can see in the
package.json of our boilerplate, it contains a command called "test:update". This command
goes through all the test cases and will update the snap whenever it is required.

More information can be found here: https://facebook.github.io/jest/docs/en/snapshot-


testing.html#content

56
Testing stateful components using Enzyme

Testing stateful components using


Enzyme
We talked about testing presentational components using our beloved feature Snapshot
testing in Jest. But it only tests the UI of the component (the render method).

What if your component contains some class methods? What if your component contains
state?

That's where we use Enzyme.

What's Enzyme?
Enzyme is a JavaScript testing utility for React. You will mostly be using the shallow utility
from Enzyme. Shallow utility helps us in rendering a component and allowing us access to
the class methods/state of the component.

Integrating Enzyme in your current Jest Framework


The default react-native boilerplate comes with Jest. Integrating Enzyme with Jest is just a
two-step process.

Install enzyme, jest-enzyme, enzyme-adapters yarn add enzyme jest-enzyme enzyme-


adapter-react-16 enzyme-react-16-adapter-setup --dev

Setup enzyme-react-adaptor and jest-enzyme. Your new package.json should look


something like this:

package.json

{
...,
"jest": {
...,
"setupTestFrameworkScriptFile": "./node_modules/jest-enzyme/lib/index.js",
"setupFiles": ["enzyme-react-16-adapter-setup"]
}
}

That's it. You can start using Enzyme utilities now.

Using Shallow renderer from Enzyme:

57
Testing stateful components using Enzyme

First, we need to shallow render our component.

import {shallow} from 'enzyme';


describe('SomeComponent component', () => {
it('Shallow rendering', () => {
const wrapper = shallow(<SomeComponent {..props}/>);
});
});

Now, our component is rendered and we can access props/state/methods using wrapper .
Here is how you can access them:

import {shallow} from 'enzyme';


describe('SomeComponent component', () => {
it('Shallow rendering', () => {
const wrapper = shallow(<SomeComponent someProp={1}/>);
const componentInstance = wrapper.instance();
//Accessing react lifecyle methods
componentInstance.componentDidMount();
componentInstance.componentWillMount();
//Accessing component state
expect(wrapper.state('someStateKey')).toBe(true);
//Accessing component props
expect(wrapper.props.someProp).toEqual(1);
//Accessing class methods
expect(componentInstance.counter(1)).toEqual(2);
});
});

As you can see, you can access everything a component possesses using shallow utitity.
You can also have a look at the example test case in our boilerplate code here.

Example
Let's take an example of a component with state and class methods. We will write test cases
for the methods including a snapshot test. The example includes testing class methods,
state, and props.

58
Testing stateful components using Enzyme

import React from 'react';


import renderer from 'react-test-renderer';
import {shallow} from 'enzyme';
import Counter from '../Counter.component';

describe('Counter component', () => {


it('Counter: renders correctly', () => {
const tree = renderer.create(<Counter />).toJSON();
expect(tree).toMatchSnapshot();
});
it('componentWillMount: should set the passed initialCountValue to state', () => {
const wrapper = shallow(<Counter initialCountValue={2}/>);
expect(wrapper.instance().state.count).toBe(2);
});
it('incrementCounter: should increment state.count by 1', () => {
const wrapper = shallow(<Counter initialCountValue={0}/>);
const instance = wrapper.instance();
expect(instance.state.count).toBe(0);
instance.incrementCounter();
expect(instance.state.count).toBe(1);
});
it('decrementCounter: should decrement state.count by 1', () => {
const wrapper = shallow(<Counter initialCountValue={1}/>);
const instance = wrapper.instance();
expect(instance.state.count).toBe(1);
instance.decrementCounter();
expect(instance.state.count).toBe(0);
});
it('should call props on increment/decrement', () => {
const incrementSpy = jest.fn();
const decrementSpy = jest.fn();
const wrapper = shallow(<Counter initialCountValue={1} onIncrement={incrementSpy}
onDecrement={decrementSpy}/>);
const instance = wrapper.instance();
instance.incrementCounter();
expect(incrementSpy).toBeCalledWith(2);
instance.decrementCounter();
expect(decrementSpy).toBeCalledWith(1);
});
});

59
Mocking RN modules

Mocking modules in Jest


Like other testing frameworks, Jest also supports mocking modules, both Node.js modules
and custom JS modules.

Most of the React Native specific utilities are automatically mocked for us since we are using
react-native preset. If you install any other modules which aren't automatically mocked, say
a react-native module which has a dependency on NativeModules, you would need to mock
the module manually.

How do you mock manually


While Jest supports a bunch of methods for mocking utilities, we suggest doing manual
mocks by creating a file with the same filename inside a __mocks__ folder, adjacent to the
module which we are mocking.

├── config
├── __mocks__
│ └── react-native-exception-handler.js
├── utils
│ ├── __mocks__
│ │ └── language.util.js
│ └── language.util.js
└── node_modules

As you can see above, you can mock both nodemodules and your custom defined JS files.
Whenever you import utils/language.util file, Jest will automatically read it from
`_mocks/language.util.js`.

If you would like to un-mock a file and read the real source code instead of mocking it, you
could do that using jest.unmock('module_path') function in the test case.

Note: You can also use the automock feature of Jest by using a function called
jest.genMockFromModule . But this might not work for some of the modules which return

nested methods and it will only mock the functions which a module exports.

Example of automock:
Let's say you want to mock the functions exposed by a Node.js module called react-native-
exception-handler

60
Mocking RN modules

Since it is a nodemodule, we would need to put the mock file inside `_mocks/react-native-
exception-handler`. Refer to the tree above to know where this file will lie.

To automock the module, our file would look something like this:

__mocks__/react-native-exception-handler.js

const mockedModule = jest.mock('react-native-exception-handler');

module.exports = mockedModule;

If we want to mock this module manually, we need to know all the functions the module
exports and mock them individually. Our file would look something like this:

__mocks__/react-native-exception-handler.js

export default {
setJSExceptionHandler: jest.fn(),
getJSExceptionHandler: jest.fn(),
setNativeExceptionHandler: jest.fn()
};

Then in the test file, we will say that we want to mock a module by doing:

jest.mock('module_path');

NPM modules inside the mock folder residing adjacent to the node_modules are
mocked by default. Hence we do not need to mock them separately.

To know more about the manual mocking in Jest, please visit here.

61
Styling

Styling in React Native


In React Native we use StyleSheets to write our styling. StyleSheets can be thought of as a
subset of CSS along with a few additional helpers for RN. StyleSheets, unlike CSS, are pure
JavaScript objects. This might confuse some web developers in the beginning and introduce
a slight learning curve. StyleSheets in React Native are used somewhat similarly to inline
styles in web. Hence, you do not have access to pseudo-classes like :hover , :active ,
etc.

Example
To add padding and border to a span in CSS we will write:

.button {
padding: 10px;
text-align: : center;
border: 1px solid black;
}

Then we will add this class to our span like this:

<span class="button"> Test </span>

Resulting in :

Test

In React Native, there is no concept of pixels or classes. Instead, our sizes will be specified
in "units" which will then be translated to pixels based on the pixel density of the screen
automatically by RN. If we write the same View in a React Native StyleSheet, it would look
something like this:

62
Styling

styles.js

import { StyleSheet } from 'react-native';


export default StyleSheet.create({
button: {
padding: 10,
textAlign: 'center',
borderWidth: 1,
borderColor: 'black'
}
});

These styles can be added to our View using:

import styles from './styles.js';

...
...
...
<View style={styles.button}></View>

Note that since we are writing JavaScript objects:

We write each style attribute in its camel-cased version of CSS


We import our StyleSheet into our components and used them by declaring a style
attribute as shown instead of classes.
There are no shorthands like border:'1px solid black'

Just like web, it is very easy to go wrong with CSS and end up with a style code that is
unmanageable. Hence, we would like to introduce a few basic guidelines in the next
chapters, so that we can get the most from our styles.

63
Theme Variables

Theme Variables
In general, every app should have well defined font sizes, colors, spacing, etc. This is done
so that the app looks consistent across screens. Now this can be achieved by maintaining a
convention across the app. For example, the devs and UX designers can decide fontSize
as:

16 - Large
14 - Medium
12 - Small

Hence our stylesheet may look like this:

styles.js

import { StyleSheet } from 'react-native';


export default StyleSheet.create({
largeButtonText: {
fontSize: 16,
fontWeight: 'bold'
},
largeHeaderText:{
fontSize: 16
},
mediumHeaderText: {
fontSize: 14,
color:'blue'
}
});

Even though this convention is good and will help you maintain consistency in small,
medium and large text sizes across the app, it does have a few fundamental flaws:

Business/marketing team may come up with a change in the requirement that the large
font size needs to be 18 instead of 16.

Now as a developer, you will need to make changes in the entire app and replace every
instance of fontSize:16 with fontSize:18, which kind of sucks!.

A new developer who joins the team might not be aware of all the conventions followed
by the team and may create a component with fontSize other than 12, 14 or 16, thus
accidentaly overlooking the code standard/convention.

Enter theme variables .

64
Theme Variables

In order to solve the above-mentioned issues, we introduce a common file theme.style.js


which will be located at app/styles/theme.style.js .

In the theme file we define our theme variables as follows:

app/styles/theme.style.js

export default {
FONT_SIZE_SMALL: 12,
FONT_SIZE_MEDIUM: 14,
FONT_SIZE_LARGE: 16,
PRIMARY_COLOR: 'rgb(30, 147, 242)',
SECONDARY_COLOR: 'rgb(238, 167, 2)',
FONT_WEIGHT_LIGHT: 200,
FONT_WEIGHT_MEDIUM: 600,
FONT_WEIGHT_HEAVY: 800
};

and we can use them like this:

styles.js

import { StyleSheet } from 'react-native';


import theme from '../styles/theme.style.js';

export default StyleSheet.create({


largeButtonText: {
fontSize: theme.FONT_SIZE_LARGE,
fontWeight: theme.FONT_WEIGHT_HEAVY
},
largeHeaderText:{
fontSize: theme.FONT_SIZE_LARGE
},
mediumHeaderText: {
fontSize: theme.FONT_SIZE_MEDIUM,
color:theme.PRIMARY_COLOR
}
});

Now our theme file dictates the size of fonts and the primary color, etc.

This gives us two benefits:

If our business team now tell us to change the font sizes, we can change the theme
variables at one place and it gets reflected in the entire app.
This will enable us to write multiple theme files which in turn adds basic theming support
to our app. For example we can write two theme files - one for a light theme and one for
a dark theme and give our app users the option to switch between the themes.

65
Theme Variables

Integrating theme variables into our NoteTaker demo


Enough of theory. Let's try and inculcate this concept into our demo app.

First of all, add the file app/style/theme.style.js .

app/style/theme.style.js

export default {
PRIMARY_COLOR: '#2aabb8',
FONT_SIZE_SMALL: 12,
FONT_SIZE_MEDIUM: 14,
FONT_SIZE_LARGE: 16,
FONT_WEIGHT_LIGHT: '200',
FONT_WEIGHT_MEDIUM: '500',
FONT_WEIGHT_BOLD: '700',
BACKGROUND_COLOR_LIGHT: '#f0f6f7',
CONTAINER_PADDING: 20
};

Now we need to modify our components to use the theme variables.

Modify the following files:

app/components/TextArea/TextArea.component.style.js

import {StyleSheet} from 'react-native';


import theme from '../../styles/theme.style';

export default StyleSheet.create({


textArea: {
fontSize: theme.FONT_SIZE_MEDIUM,
fontWeight: theme.FONT_WEIGHT_LIGHT
}
});

app/components/Home/Home.component.js

66
Theme Variables

import React, {Component} from 'react';


import {View, Text} from 'react-native';
import styles from './Home.component.style';
import TextArea from '../TextArea/TextArea.component';

class Home extends Component {


render () {
return (
<View style={styles.container}>
<Text style={styles.textAreaTitle}> Please enter your note here</Text>
<TextArea style={styles.textArea}/>
</View>
);
}
}

export default Home;

app/components/Home/Home.component.style.js

import {StyleSheet} from 'react-native';


import theme from '../../styles/theme.style';

export default StyleSheet.create({


container: {
flex: 1,
paddingVertical: theme.CONTAINER_PADDING,
alignItems: 'center'
},
textAreaTitle: {
fontSize: theme.FONT_SIZE_MEDIUM,
fontWeight: theme.FONT_WEIGHT_BOLD,
alignSelf: 'flex-start',
padding: 10
},
textArea: {
flex: 1,
padding: theme.CONTAINER_PADDING,
alignSelf: 'stretch',
overflow: 'scroll',
backgroundColor: theme.BACKGROUND_COLOR_LIGHT
}
});

Our app should now look like this:

67
Theme Variables

68
Theme Variables

The code till here can be found on the branch chapter/8/8.1

69
Common Styles/Mixins

Common styles
In React Native, each component is styled using inline styles. This means that it becomes
slightly tricky to share styles as you can in web.

In web, we write a class

.btn {
padding: 10;
border: '1px solid black';
}

Now if we want to apply this class to two different divs we will do so as follows:

<div class='btn first-btn'>First button</div>


<div class='btn second-btn'>Second button</div>

The same is possible in React Native in the following way:


styles.js

import { StyleSheet } from 'react-native';


export default StyleSheet.create({
btn: {
padding: 10,
borderWidth: 1
},
firstBtn:{
...
...
},
secondBtn:{
...
...
}
});

and we will add styles to our View by:

import styles from './styles.js';

70
Common Styles/Mixins

...
...
...
<View>
<View style={[styles.btn, styles.firstBtn]}>First button</View>
<View style={[styles.btn, styles.secondBtn]}>Second button</View>
</View>

This solves the problem only if the style objects are in the same component because in RN
we do not import styles from other components (each component has its own style). But in
web, we could have just reused the class anywhere (since css is global).

To solve the problem of reusable styles in React Native, we introduce another file named
app/style/common.style.js This is where we will write our mixins/common styles.

Hence, if all the buttons in our app have a similar style we can write a style with similar
properties inside the common.style.js
app/style/common.style.js

import { StyleSheet } from 'react-native';


export default StyleSheet.create({
btn: {
padding: 10,
borderWidth: 1
}
});

And we can just import this in our component style files and reuse them directly like this:
styles.js

import { StyleSheet } from 'react-native';


import common from '../style/common.style.js';
export default StyleSheet.create({
firstBtn:{
...common.btn,
backgroundColor: 'blue'
},
secondBtn:{
...common.btn,
backgroundColor: 'red'
}
});

and we will add styles to our View like this:

71
Common Styles/Mixins

import styles from './styles.js';

...
...
...
<View>
<View style={styles.firstBtn}>First button</View>
<View style={styles.secondBtn}>Second button</View>
</View>

This way our mixins/common style file will provide us the base styles which are common
across the app and we write component specific styles in the component style file. This
allows significant style reuse and avoids code duplication.

Integrating common/mixin styles into our NoteTaker demo


Before we go into common styles, let's modify our code a bit to add another component for
entering a title for our note. Modify the files as follows:
app/components/Home/Home.component.js

import React, {Component} from 'react';


import {View, Text, TextInput} from 'react-native';
import styles from './Home.component.style';
import TextArea from '../TextArea/TextArea.component';

class Home extends Component {


state = {
title: '' // adding the state here temporarily for illustration purposes
}
setTitle = (title) => this.setState({title})
render () {
return (
<View style={styles.container}>
<Text style={styles.titleHeading}> Note Title</Text>
<TextInput style={styles.titleTextInput}
onChangeText={this.setTitle} value={this.state.title} />
<Text style={styles.textAreaTitle}> Please type your note below </Text>
<TextArea style={styles.textArea}/>
</View>
);
}
}

export default Home;

app/components/Home/Home.component.style.js

72
Common Styles/Mixins

import {StyleSheet} from 'react-native';


import theme from '../../styles/theme.style';

export default StyleSheet.create({


container: {
flex: 1,
paddingVertical: theme.CONTAINER_PADDING,
alignItems: 'center'
},
titleHeading: {
fontSize: theme.FONT_SIZE_MEDIUM,
alignSelf: 'flex-start',
padding: 10,
fontWeight: theme.FONT_WEIGHT_BOLD,
},
titleTextInput: {
padding: theme.TEXT_INPUT_PADDING,
backgroundColor: theme.BACKGROUND_COLOR_LIGHT,
alignSelf: 'stretch'
},
textAreaTitle: {
fontSize: theme.FONT_SIZE_MEDIUM,
alignSelf: 'flex-start',
padding: 10,
fontWeight: theme.FONT_WEIGHT_LIGHT,
fontStyle: 'italic'
},
textArea: {
padding: theme.TEXT_INPUT_PADDING,
backgroundColor: theme.BACKGROUND_COLOR_LIGHT,
alignSelf: 'stretch',
flex: 1
}
});

app/styles/theme.style.js

export default {
PRIMARY_COLOR: '#2aabb8',
FONT_SIZE_SMALL: 12,
FONT_SIZE_MEDIUM: 14,
FONT_SIZE_LARGE: 16,
FONT_WEIGHT_LIGHT: '200',
FONT_WEIGHT_MEDIUM: '500',
FONT_WEIGHT_BOLD: '700',
BACKGROUND_COLOR_LIGHT: '#f0f6f7',
CONTAINER_PADDING: 20,
TEXT_INPUT_PADDING: 10
};

73
Common Styles/Mixins

Our app should now look like this:

If you notice, even though we have a theme file, our style code has a lot of duplicated code.
This is primarily because we are repeating our styling for text input and also for the heading.

The solution to this problem, as discussed before, is common styles/mixins.

Let's create the file common.style.js


app/styles/common.style.js

74
Common Styles/Mixins

import theme from './theme.style';

export const headingText = {


fontSize: theme.FONT_SIZE_MEDIUM,
alignSelf: 'flex-start',
padding: 10,
fontWeight: theme.FONT_WEIGHT_BOLD,
};

export const textInput = {


padding: theme.TEXT_INPUT_PADDING,
backgroundColor: theme.BACKGROUND_COLOR_LIGHT,
alignSelf: 'stretch'
};

And modify our component style files to include the common.style.js


app/components/Home/Home.component.style.js

import {StyleSheet} from 'react-native';


import theme from '../../styles/theme.style';
import {headingText, textInput} from '../../styles/common.style';

export default StyleSheet.create({


container: {
flex: 1,
paddingVertical: theme.CONTAINER_PADDING,
alignItems: 'center'
},
titleHeading: {
...headingText
},
titleTextInput: {
...textInput
},
textAreaTitle: {
...headingText,
fontWeight: theme.FONT_WEIGHT_LIGHT,
fontStyle: 'italic'
},
textArea: {
...textInput,
flex: 1
}
});

If you see our style code looks much more concise and we are resuing the styles for similar
components with slight style changes. Hence, we import our base styles for the components
from common.style.js and add our custom styles later on top of it. This way we reduce our

75
Common Styles/Mixins

work and minimize code duplication.

We see no change in the output but our code becomes much cleaner.

76
Common Styles/Mixins

The code till here can be found on the branch chapter/8/8.2

77
Separating styles from component

Separating styles from component code


Let's assume we want to build a Button component.

A simple button will look something like this:

Button.component.js

import React, { Component } from 'react';


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

class Button extends Component {


render() {
return (
<View style={styles.container}>
<Text style={styles.buttonText}> Press Me! </Text>
</View>
);
}
}

const styles = StyleSheet.create({


container: {
padding: 10,
alignItems:'center',
justifyContent:'center',
backgroundColor: '#43a1c9',
},
buttonText: {
fontSize: 20,
textAlign: 'center'
}
});

export default Button;

This will produce a nice looking button component. But we suggest that you move the styles
to a different file Button.component.style.js .

Modifying the code we get.

Button.component.js

78
Separating styles from component

import React, { Component } from 'react';


import { StyleSheet, Text, View} from 'react-native';
import styles from './Button.component.style.js';

class Button extends Component {


render() {
return (
<View style={styles.container}>
<Text style={styles.buttonText}> Press Me! </Text>
</View>
);
}
}

export default Button;

Button.component.style.js

export default StyleSheet.create({


container: {
padding: 10,
alignItems:'center',
justifyContent:'center',
backgroundColor: '#43a1c9',
},
buttonText: {
fontSize: 20,
textAlign: 'center'
}
});

This has a few benefits:

This makes the component code much cleaner. The style is present in its own separate
file.
This allows you to write two different style files for Android and iOS if required. Thus,
you can keep the same functionality but the button can look different based on the
requirement for the platform.

For example:

Button.component.js

79
Separating styles from component

import React, { Component } from 'react';


import { StyleSheet, Text, View} from 'react-native';
import styles from './Button.component.style.js';

class Button extends Component {


render() {
return (
<View style={styles.container}>
<Text style={styles.buttonText}> Press Me! </Text>
</View>
);
}
}

export default Button;

iOS specific style

Button.component.style.ios.js

export default StyleSheet.create({


container: {
padding: 10,
alignItems:'center',
justifyContent:'center',
backgroundColor: '#43a1c9',
},
buttonText: {
fontSize: 20,
textAlign: 'center'
}
});

Android specific style

Button.component.style.android.js

80
Separating styles from component

export default StyleSheet.create({


container: {
padding: 10,
borderWidth: 1,
alignItems:'center',
justifyContent:'center',
backgroundColor: '#d2843b'
},
buttonText: {
fontSize: 20,
textAlign: 'center'
}
});

Thus by simply moving the styles into a separate file, we could achieve a style code that
behaves exactly the way we needed on different platforms. Also, we could reuse the
component logic.

Conclusion
In Web, we have lots of production grade tools like Sass, Less, etc which allow us to write
modular, scoped CSS which is easier to manage. These tools then take care of building all
our style code into one cohesive stylesheet for our entire application. In React Native, we
must think of styling in a slightly different manner. By doing some pre-planning and
organization before writing the code for the components, we can reduce code duplication
and unnecessary confusions. It takes a bit of getting used to, but styling in React Native is
as powerful as CSS for the web and is the fastest way to build multi-platform native
applications.

81
Redux

Redux - The State Container


We assume that the reader is already aware of Redux. But just to recap, according to
redux.js.org:

Redux is a predictable state container for JavaScript apps. It helps you write
applications that behave consistently, run in different environments (client, server, and
native), and are easy to test. On top of that, it provides a great developer experience,
such as live code editing combined with a time traveling debugger.

In a nutshell, Redux is a state container. That means, it will contain all our runtime
application state or data.

Redux is essentially made up of three parts:

Store - The store contains a global state for the entire app. It is basically the manager of
the application state.
Actions - These are the commands you pass to the store along with some data to
modify the stored state.
Reducers - Reducers are functions that the store calls whenever an action arrives. The
reducers determine what the new state will be based on the action and the action
payload they receive.

But we already have React's state


Both Redux and React's state are used to manage the state in the application. But, both
Redux and React's state are very different in the way they work and it is good to know when
to use what.

React state is stored locally within a component. When it needs to be shared with other
components, it is passed down through the props. This means that all the components which
need the state data need to be children of the component holding the value. But in case of
Redux, the state is stored globally in the Redux store. Components subscribe to the store to
get access to the value. This centralizes all data and makes it very easy for a component to
get the state it needs, without surrounding components being affected.

So, does this means that Redux is great and we should use it for all our app state
management?

NO!

82
Redux

While Redux is helpful in some cases, it will create unnecessary indirections for simpler and
trivial use cases.

Consider that we have a text input. And since we are using Redux, we decide to use Redux
to store all the changes in the text field in the Redux store. In Redux, for changing the state
on text input, we will need to create an Action, write a reducer and then subscribe our
component to the store so that it re-renders on every state change. This is bad! Why do we
need to complicate things so much?

Dan Abramov - The creator of Redux says you might actually not need Redux unless you
have a plan to benefit from this additional indirection. In his blog at
https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367, he clearly
states that since Redux introduces an additional indirection to managing the state of the
application, we should use it only if it benefits us.

Now, let's see when we should use Redux and when React's state is good enough.

One way to do this is based on the duration the data has to be stored. As Tyler Hoffman
explains in his blog post: https://spin.atomicobject.com/2017/06/07/react-state-vs-redux-
state/,

Different pieces of state are persisted for different amounts of time. We can categorize data
into:

Short term: data that will change rapidly in your app


Medium term: data that will likely persist for a while in your app
Long term: data that should last between multiple app launches.

Short term data


Short term data is data that changes very quickly and is needed to be stored only for small
amount of time. For example, this includes the characters that a user types in a text field.
This data is not needed once the user submits the form. Also, we will most likely not need to
transfer this type of data to any other independent component. Such type of data clearly fits
the use case for React's state.

Medium term data


Medium term data needs to stick around while the user navigates through the app. This
could be data loaded from an API or any changes that need to be persisted until a page
refresh. This can also contain data that needs to be used by a component that is completely
unrelated to the component that produced the data. As an example, consider that one of the
components makes an API call on a button click to update the user's profile details. The data

83
Redux

returned from the server needs to be stored and will be used by a completely unrelated
profile screen. Now, if the data is stored in some global location, it will be far easier to
access this data. Such type of use cases clearly fits Redux.

Long term data


This is the data that should be persisted between page refreshes or when the user closes
and reopens the app. Since the Redux store is created on app launch, this type of data
should be stored somewhere else, for example Async Storage in the case of React Native.

84
Redux setup

Setting up Redux for React Native


Why Redux?
Let's say in our note-taker app, we need to add the character count and a save/sync button
at the bottom of the screen. First, let's create the UI component for it.

Modify the files

app/components/Home/Home.component.js

...
...
...
onChangeText={this.setTitle} value={this.state.title} />
<Text style={styles.textAreaTitle}> Please type your note below </Text>
<TextArea style={styles.textArea}/>
<View style={styles.bottomBar}>
<View style={styles.bottomBarWrapper}>
<Text style={styles.saveBtn}>Save</Text>
<Text style={styles.characterCount}>{20} characters</Text>
</View>
</View>
</View>
);
}

app/components/Home/Home.component.style.js

85
Redux setup

export default StyleSheet.create({


container: {
flex: 1,
paddingTop: theme.CONTAINER_PADDING,
flexDirection: 'column',
justifyContent: 'space-between'
},
...
...
...
...
textArea: {
...textInput,
flex: 1
},
bottomBar: {
flexDirection: 'row',
alignItems: 'center'
},
bottomBarWrapper: {
flexDirection: 'row',
justifyContent: 'space-between',
flex: 1
},
saveBtn: {
padding: 10,
fontWeight: theme.FONT_WEIGHT_BOLD
},
characterCount: {
padding: 10,
fontSize: theme.FONT_SIZE_SMALL
}
});

app/styles/theme.style.js

...
...
BACKGROUND_COLOR_LIGHT: '#ebe9e9',
...
...
};

Now our app should have a bottom bar with a character count and a save button.

Currently, we have hardcoded the character count to 20.

86
Redux setup

But if you look at the app now, there is no way for us to get the character count from the
TextArea component and use it as the text for the character count text view. To do this we
will need to move the state present inside the TextArea component and place it in the
Home component. This is because all the components that need access to a state have to
be children of the component holding the state.

So we modify our components as follows:

app/components/TextArea/TextArea.component.js

87
Redux setup

import React, {Component} from 'react';


import {TextInput} from 'react-native';
import PropTypes from 'prop-types';
import styles from './TextArea.component.style';

class TextArea extends Component {


static propTypes = {
text: PropTypes.string,
onTextChange: PropTypes.func
}
render () {
const {text, onTextChange, ...extraProps} = this.props;
return (
<TextInput
{...extraProps}
style={[styles.textArea, extraProps.style]}
multiline = {true}
onChangeText={onTextChange}
value={text}
/>
);
}
}
export default TextArea;

app/components/Home/Home.component.js

88
Redux setup

import React, {Component} from 'react';


import {View, Text, TextInput} from 'react-native';
import styles from './Home.component.style';
import TextArea from '../TextArea/TextArea.component';

class Home extends Component {


state = {
title: '',
text: ''
}
setTitle = (title) => this.setState({title})
setText = (text) => this.setState({text});
render () {
return (
<View style={styles.container}>
<Text style={styles.titleHeading}> Note Title</Text>
<TextInput style={styles.titleTextInput}
onChangeText={this.setTitle} value={this.state.title} />
<Text style={styles.textAreaTitle}> Please type your note below </Text>
<TextArea text={this.state.text} onTextChange={this.setText} style={styles.tex
tArea}/>
<View style={styles.bottomBar}>
<View style={styles.bottomBarWrapper}>
<Text style={styles.saveBtn}>Save</Text>
<Text style={styles.characterCount}>{this.state.text.length} characters</T
ext>
</View>
</View>
</View>
);
}
}
export default Home;

The character count should now update whenever you enter text on the text field.

89
Redux setup

By moving the state from the child component to the parent, we were able to access it in
multiple children components.

Therefore, to provide access to the data that needs to be accessed by multiple components,
we need to have the state in the enclosing parent component. Following this principle, if we
keep on moving the state to the parent component, we will end up with the state in the
topmost level component.

Redux builds on top of similar principles. It keeps a global store to which the components
which need access to the data can subscribe. Additionally, it provides a mechanism by
which these components can re-render whenever the data in the store changes.

Now, since we understand how Redux is helpful, let's setup Redux for our app.

Setup
Let's begin by installing a few packages.
yarn add redux react-redux redux-promise redux-thunk

90
Redux setup

or
npm install --save redux react-redux

redux - the main Redux library.


react-redux - the React bindings for Redux, which makes our life easy when using
Redux with React.

Additionally, you can also install your preferred Redux middleware like redux-thunk , etc.
The comments on the code specify how to do that.

Now create the files

app/redux/store.js

import {createStore, compose/* , applyMiddleware*/} from 'redux';


// import someReduxMiddleware from 'some-redux-middleware';
// import someOtherReduxMiddleware from 'some-other-redux-middleware';
import rootReducer from './reducers/root.reducer';

const enhancerList = [];


const devToolsExtension = window && window.__REDUX_DEVTOOLS_EXTENSION__;

if (typeof devToolsExtension === 'function') {


enhancerList.push(devToolsExtension());
}

const composedEnhancer = compose(/* applyMiddleware(someReduxMiddleware, someOtherRedu


xMiddleware),*/ ...enhancerList);

const initStore = () => createStore(rootReducer, {}, composedEnhancer);

module.exports = {
initStore
};

app/redux/reducers/root.reducer.js

import {combineReducers} from 'redux';

export default combineReducers({

});

Now, let's add our first reducer and action.

Create the files:

91
Redux setup

app/redux/actions/index.actions.js

This file will contain all our actions.

export const TEST_ACTION = 'TEST_ACTION';

app/redux/reducers/test.reducer.js

import {TEST_ACTION} from '../actions/index.actions';

const test = (state = {}, action) => {


switch (action.type) {
case TEST_ACTION: {
return action.payload;
}
default:
return state;
}
};

export default test;

Now let's add our test reducer to the root reducer.

Modify

app/redux/reducers/root.reducer.js

import {combineReducers} from 'redux';


import test from './test.reducer';

export default combineReducers({


test
});

Now let's initialize the store.

Modify the file:

app/index.js

92
Redux setup

import React, {Component} from 'react';


import {initStore} from './redux/store';
import {Provider} from 'react-redux';

import App from './App.container';

const store = initStore();

class NoteTaker extends Component {


render () {
return (
<Provider store={store}>
<App />
</Provider>
);
}
}

export default NoteTaker;

and move the initialization of the home component to another file.

app/App.container.js

import React, {Component} from 'react';


import Home from './components/Home/Home.component';
import {connect} from 'react-redux';

class App extends Component {


render () {
console.log(this.props.state); // eslint-disable-line
return (
<Home />
);
}
}

const mapStateToProps = (state) => ({


state
});

const mapDispatchToProps = (dispatch) => ({


dispatch
});

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

At this point, we should a have a Redux store with an initial test state from the test reducer.

To check this, let's run our app on the simulator.

93
Redux setup

Now open up the debug menu on the iOS simulator by pressing cmd+ctrl+z or on Android
emulator by using cmd+m .

Choose Debug JS Remotely .

This should run the app JS code in react-native-debugger and if all goes well we should see
something like this on the console panel:

This implies that our Redux store is successfully initialized with the test reducer.

NOTE: If your tests fail due to the error window not defined , then add a mock file

__mocks__/react-native.js

var rn = require('react-native');
global.window = global;
module.exports = rn;

This will initialize a dummy window variable when tests are run in node environment.

94
Redux setup

The code till here can be found on the branch chapter/9/9.1

95
Presentational VS Containers

Presentational Components vs Container


Components
When writing code in React or React Native, we often wonder how to structure our code so
that it makes our life much easier when handling state changes, data flow and renders, etc.
There is a pattern which helps in organizing React based applications - splitting the
components into presentational and containers.

Presentational components
Presentational components are those components whose only job is to render a view
according to the styling and data passed to them. Essentially, they do not contain any
business logic. That's why they are sometimes also called dumb components . This means
that they don't have direct access to Redux or other data stores. Data is passed to them via
props.

According to Dan Abramov in his blog https://medium.com/@dan_abramov/smart-and-


dumb-components-7ca2f9a7c7d0, presentational components:

are concerned with how things look.


have markup and styles of their own.
have no dependencies on the rest of the app, such as Redux stores.
don’t specify how the data is loaded or mutated.
receive data and callbacks exclusively via props.
rarely have their own state (when they do, it’s UI state rather than data).

An example of a Dumb/Presentational component would be:

96
Presentational VS Containers

import React, {Component} from 'react';


import {View} from 'react-native';
import styles from './Header.component.style';

class Header extends Component {


render () {
const {title, subtitle} = this.props;
return (
<View style={styles.container}>
<View style={styles.titleHeading}>{title}</View>
<View style={styles.subtitle}>{subtitle}</View>
</View>
);
}
}
export default Header;

Container components
Container components are those React components which have access to the store. These
components make API calls, do processing and contain the business logic of the app.
Container components shouldn't have the view logic or presentational logic. The job of
container components is to compute the values and pass them as props to the
presentational components. Hence, these components are sometimes also referred to as
Smart Components.

Therefore, container components:

are concerned with how things work.


don’t usually have any markup of their own except for some wrapping Views, and never
have any styles.
provide the data and behavior to presentational or other container components.
call Redux actions and provide these as callbacks to the presentational components.
are often stateful, as they tend to serve as data sources.
are usually generated using higher order components such as connect() from React
Redux, createContainer() from Relay, or Container.create() from Flux Utils, rather than
written by hand.

An example of a Smart/Container component would be:

97
Presentational VS Containers

import React, {Component} from 'react';


import Header from '../component/Header.component';

class Home extends Component {


calculateSomething = () => {
...some calculation / api calls....
}
render () {
const {title, subtitle, goToLogin} = this.props;
return (
<Header title={title} subtitle={subtitle} goToLogin={goToLogin} calculateSometh
ing={this.calculateSomething}/>
);
}
}
const mapStateToProps = (state)=>{
return {
title: state.title,
subtitle: state.subtitle
};
};

const mapDispatchToProps = (dispatch) => {


goToLogin: () => dispatch({action:'GO_TO_LOGIN'})
};

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

In the above example, if you notice, our Container component does not do any kind of
layouting or styling. It only manages the business logic. This helps us separate the concerns
"Styling/Layouting" and "Business Logic".

The Container-Presentational pattern gives us many benefits:

Less code duplication. Because you are forced to move all the layout components out
as separate presentational components, you can now directly reuse them instead of
copy-pasting the code in every page.
Presentational components are essentially your app’s View layer. Hence, you can
change the styling without touching the app's logic.
Better separation of concerns. You understand your app and your UI better by writing
components this way.
Better reusability. You can use the same presentational component with completely
different state sources, and turn those into separate container components that can be
further reused.

Back to Code

98
Presentational VS Containers

Let's organize our project to include presentational and container components pattern.

First, let's add a new reducer to manage our content (title and text).

Modify the action file to include these:

app/redux/actions/index.actions.js

import {createAction} from 'redux-actions';

export const TEST_ACTION = 'TEST_ACTION';


export const SET_TEXT = 'SET_TEXT';
export const SET_TITLE = 'SET_TITLE';

export const setTitle = createAction(SET_TITLE);


/* This is equivalent to
export const setTitle = (payload) => {
return {
type: SET_TITLE,
payload: payload
};
};
*/
export const setText = createAction(SET_TEXT);

Notice the use of createAction . As the comment says, we are essentially replacing:

export const setTitle = (payload) => {


return {
type: SET_TITLE,
payload: payload
};
};

with

export const setTitle = createAction(SET_TITLE);

To do this we need to include another package.

yarn add redux-actions

Now, let's create the corresponding reducer.

app/redux/reducers/content.reducer.js

99
Presentational VS Containers

import {SET_TEXT, SET_TITLE} from '../actions/index.actions';

const defaultState = {
text: '',
title: ''
};

const content = (state = defaultState, action) => {


switch (action.type) {
case SET_TEXT: {
return {...state, text: action.payload};
}
case SET_TITLE: {
return {...state, title: action.payload};
}
default:
return state;
}
};

export default content;

Now, let's add the reducer to the root reducer.

app/redux/reducers/root.reducer.js

import {combineReducers} from 'redux';


import test from './test.reducer';
import content from './content.reducer';

export default combineReducers({


test,
content
});

Finally, it's time to create our first Smart component.

Create a new file under /pages with the name Home.page.js

app/pages/Home.page.js

100
Presentational VS Containers

import React, {Component} from 'react';


import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {setTitle, setText} from '../redux/actions/index.actions';
import Home from '../components/Home/Home.component';

class HomePage extends Component {


render () {
const {setTitle, setText, title, text} = this.props;
return (
<Home setTitle={setTitle} setText={setText} title={title} text={text} />
);
}
}

HomePage.propTypes = {
setTitle: PropTypes.func,
setText: PropTypes.func,
title: PropTypes.string,
text: PropTypes.string
};

const mapStateToProps = (state) => ({


title: state.content.title,
text: state.content.text
});

const mapDispatchToProps = (dispatch) => ({


setTitle: (title) => dispatch(setTitle(title)),
setText: (text) => dispatch(setText(text)),
});

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

If you notice the job of Home Page is just to fetch data and provide logical functions to the
view layer.

The corresponding view layer would look like this:

app/components/Home/Home.component.js

101
Presentational VS Containers

import React, {Component} from 'react';


import {View, Text, TextInput} from 'react-native';
import styles from './Home.component.style';
import TextArea from '../TextArea/TextArea.component';
import PropTypes from 'prop-types';

class Home extends Component {


render () {
const {setTitle, title, text, setText} = this.props;
return (
<View style={styles.container}>
<Text style={styles.titleHeading}> Note Title</Text>
<TextInput style={styles.titleTextInput}
onChangeText={setTitle} value={title} />
<Text style={styles.textAreaTitle}> Please type your note below </Text>
<TextArea text={text} onTextChange={setText} style={styles.textArea}/>
<View style={styles.bottomBar}>
<View style={styles.bottomBarWrapper}>
<Text style={styles.saveBtn}>Save</Text>
<Text style={styles.characterCount}>{text.length} characters</Text>
</View>
</View>
</View>
);
}
}

Home.propTypes = {
setTitle: PropTypes.func,
setText: PropTypes.func,
title: PropTypes.string,
text: PropTypes.string
};

export default Home;

Finally, let's modify our app container to include the page instead of the component.

app/App.container.js

102
Presentational VS Containers

import React, {Component} from 'react';


import Home from './pages/Home.page';
import {connect} from 'react-redux';

class App extends Component {


render () {
return (
<Home />
);
}
}

export default connect(null, null)(App);

Our app should now look like this:

Although the app looks exactly the same, it is working in a slightly different manner.

If you paid attention you would notice:

We now have a clear demarcation between the view and logic layer. Hence, we will
know exactly where to look in case there is either a UI or logical bug.
We are not importing react-native at all in any part of the whole code base except in

103
Presentational VS Containers

app/components/ . This means porting the project to any other platform like Electron is

as simple as rewriting the app/components directory. The logical layer remains intact.
The code is easier to maintain as the flow of data to the view layer happens via props.

The code till here can be found on the branch chapter/9/9.2

104
Navigation

Navigation
Navigation in react-native is pretty different, especially if you are coming from a web
background. It follows native standards for navigation. For example, it uses the concept of
screens, stacks, push/pop, which most web developers have not used for routing.

It is not as simple as doing dispatch(push('<routeName>') . You have to manage


screens, reset your stack, nest them properly. If not configured well, it can cause huge
performance loss and multiple components to be mounted at the same time.

React-native has a bunch of options for routing. The different options have been mentioned
here: https://facebook.github.io/react-native/docs/navigation.html. We have found react-
navigation to be the most stable among all of them.

Why react-navigation?
The official react-native documentation recommends using react-navigation. It says,
"You will probably want to use React Navigation." .

It supports native transitions for both Android and iOS.

Both Android and iOS routes can be handled by one single configuration file without any
platform specific configuration.

Provides multiple types of navigators out of the box. E.g.: StackNavigator, TabNavigator,
DrawerNavigator.

It is highly configurable. You can configure almost everything, be it tabs, header or


footer, using a single config file.

Redux integration is available. It's very easy to integrate with the Redux store. The
benefit of this is that you can navigate by just dispatching an action and passing the
route name. It makes route management much easier. After integration, all you need to
do is: dispatch(NavigationActions.navigate({routeName})) .

105
Using React-navigation

Using React-Navigation
Using react-navigation is pretty simple once you understand how it works. Unfortunately,
their official documentation lacks some explanations. But there is no need to worry. We will
try to fill in the gaps in the official documentation in this chapter. The official documentation is
available at https://reactnavigation.org/docs/getting-started

How does navigation work in native applications?


When it comes to native apps, the routing works on the concept of stacks, screens,
push/pop, etc.

What is a stack in navigation?


We all have heard of stacks in our college programming classes. Well, this is exactly the
same stack. Whenever you navigate from one screen to another, a new screen comes up on
top. The old screen is still there (unless you reset your stack). This mechanism of caching
the old pages helps in improving performance and making the transitions smoother. Have a
look at the image below for an example of stack in navigation.

Types of navigators offered by react-navigation

106
Using React-navigation

React-navigation offers a bunch of navigators predefined for our use. An app might contain
all the available navigators. Let's try to understand all three of them one-by-one.

StackNavigator: This is the most common navigator of all. You will use it for most react-
native applications. We just explained how stacks work in navigation. You define a
StackNavigator by passing the routes object inside the StackNavigator function from
react-navigation. Each of the screens gets mounted only when you navigate to that
particular screen and gets un-mounted only when you go back or manually reset the
navigation state.

TabNavigator: This is also quite a common user interface where we have a bunch of
tabs and can swipe between them. We have seen this in Facebook, Twitter, and many
other popular apps. React-navigation supports this navigator out of the box. The way of
defining a TabNavigator is pretty similar to a StackNavigator. The only difference is that
the routes you define in TabNavigator get mounted all at once. So you need to be a little
careful when managing navigation to/from a different stack.

DrawerNavigator: This navigator gives us the side-menu which we see in most mobile
applications nowadays. You can obviously customize everything in your drawer since it
is all written in JavaScript. You can set up the DrawerNavigator by writing only 4 lines of
code. The DrawerNavigator comes with a default UI which supports an icon and a title
per list item, which looks quite elegant. You can also define your own component to
show up as a drawer if you want to have a custom UI and actions for your side-menu.
More info can be found here.

Creating a router
Install react-navigation using npm or Yarn.

yarn add react-navigation or npm install react-navigation --save

Creating a router is pretty easy. You just define a page component (which will be a container
component) and then import it in your router.js file.

Each of the navigators accepts an object during initialization whose syntax is as follows:

const Router = StackNavigator({


<routeName, string>: {screen: <screenObject, enum(React component, navigator instanc
e)>, navigationOptions: <Screen level nav options>}
}, {
navigationOptions: <Router level default nav options>
})

107
Using React-navigation

routeName is the name associated with the current screen. It will be used for

navigation/analytics tracking.

screenObject The screen key in the router object can contain:

A React Native component.


A react-navigation router instance if you want routers to be nested.

Example routes/index.js

const AboutRoutes = TabNavigator({


aboutApp: {
screen: AboutApp,
},
aboutDevs: {
screen: AboutDevs,
}
});

const Router = StackNavigator({


home: {screen: HomePage,
navigationOptions: {
title: 'Start taking notes',
}
},
about: {
screen: AboutRoutes
}
}, {
mode: 'card'
});

export default Router;

Each of the navigators returns a React component which is supposed to be added to the
root level of the app.

Example App.container.js

108
Using React-navigation

import React, {Component} from 'react';


import Router from './routes';
import {connect} from 'react-redux';

class App extends Component {


render () {
return (
<Router />
);
}
}

export default App;

When we define a React component in our router file, it adds a few properties to the
component, which are:

static navigationOptions: We can use this to define our headers, title, etc. However, we
recommend defining this in router.js because we will be importing pages to our router
and defining header UI/title in the container component is not a good idea.

this.props.navigation: react-navigation also adds a navigation function to the screen.


This function can be used to navigate to a route and pass parameters. We do not
recommend this way as we will be handling routing via redux-actions

109
Using React-navigation

The code till here can be found on the branch chapter/10/10.1

110
Integrating with redux store

Integrating with the Redux store


React-navigation comes with the ability to integrate with Redux. Though it is optional, we
highly recommend doing this. Before reading this chapter, please make sure that you have a
basic understanding of react-navigation. If not, please have a look at this.

Why Redux integration?


React-navigation maintains its internal store to keep track of stacks, routes-history, etc., if
not configured to the Redux store. This was done so that it is easy to configure the library
and handle use cases when the app doesn't use Redux or does not require store linking.
After installing the library and configuring routes, you will see something like this in the
debugger when you navigate.

As you can see, the library maintains its own store and it logs every action in the dev mode.
The library also has the flexibility to be integrated with the Redux store of your application.

While the integration is completely optional, we highly recommend doing this if your app is
already using Redux because of the following reasons:

1. It gives us a lot more flexibility to control the route state at every point in time. Consider
a case where you want to call a function on every route change, for example for screen
tracking. You can use redux-middleware in such a scenario.

2. It makes the navigation much more readable and cleaner. You just have to dispatch an
action with the routeName to which you want to navigate.

3. It removes the dependency on needing a React component to be able to navigate.


Consider a scenario where you want to navigate from your redux-thunk or a redux-saga
file. You would be unable to do it without integrating react-navigation with Redux.

How do you integrate react-navigation to the Redux store?

111
Integrating with redux store

Integration with the Redux store is pretty easy. Let's continue with the integration in our
NoteTaker app using three simple steps.

Run npm install react-navigation-redux-helpers or yarn add react-navigation-redux-


helpers

1. In your reducer's index file, add the following:

reducers/index.js

import {combineReducers} from 'redux';


import test from './test.reducer';
import Router from '../../routes';

const router = Router.router;


const homeNavAction = router.getActionForPathAndParams('home');
const initialNavState = router.getStateForAction(homeNavAction);

const nav = (state = initialNavState, action) => {


const nextState = router.getStateForAction(action, state);
return nextState || state;
};

export default combineReducers({


test,
nav
});

Now let's go through the code:

router.getActionForPathAndParams : This function receives the key defined for a

route in Navigator and params and returns an action which is needed to update the
navigation state. In redux language, we need to call this action to navigate to a
route.
Output of this statement: Object {type: "Navigation/NAVIGATE", routeName: "home",
action: Object}

This is the action that we get for the path 'home'. This becomes the initial route of our
navigator.

router.getStateForAction(homeNavAction) : The above step gives us the action for

navigating to the initial route, now we have to update the state of the Navigator to
actually navigate to the route. So we pass the action and the current state of the
navigator to getStateForAction and it returns the new updated state.
Output of this statement:

112
Integrating with redux store

Object {key: "StackRouterRoot", isTransitioning: false, index: 0, routes: Array(


1)}
index: 0
isTransitioning: false
key: "id-1522736064605-0"
params: undefined
routeName: "home"
routes: Array(1) {
0: Object
key: "id-1522736173525-0"
params: undefined
routeName: "home"
}

1. Add React Navigation Redux Middleware to store:

import React, { Component } from 'react';


import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import { createReactNavigationReduxMiddleware } from 'react-navigation-redux-helpers';

import RouterWithNavState from './routes';

const middleware = createReactNavigationReduxMiddleware(


'root',
state => state.nav,
);

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

export default class App extends Component {


render() {
return (
<Provider store={store}>
<RouterWithNavState />
</Provider>
);
}
}

1. Modify main routes file as follows:

import React, { Component } from 'react';


import { connect } from 'react-redux';
import { StackNavigator, addNavigationHelpers } from 'react-navigation';
import { createReduxBoundAddListener } from 'react-navigation-redux-helpers';
import PropTypes from 'prop-types';

113
Integrating with redux store

//example routes
import AuthRoutes from './auth.routes';
//example component
import Home from '../Components/Home';

export const Router = StackNavigator({


home: {
screen: Home,
navigationOptions: {
header: null
}
},
auth: {
screen: AuthRoutes,
navigationOptions: {
header: null
}
}
});

class RouterWithNavState extends Component {

render() {
const addListener = createReduxBoundAddListener('root');
const { dispatch, nav } = this.props;
return (
<Router
navigation={addNavigationHelpers({ dispatch, state: nav, addListener })}
/>
);
}
}

const mapStateToProps = (state) => {


return ({
nav: state.nav
});
};

RouterWithNavState.propTypes = {
dispatch: PropTypes.func,
nav: PropTypes.object
};

export default connect(mapStateToProps)(RouterWithNavState);

After the integration, you will be able to see the navigation state and actions inside your
debugger's store.

114
Integrating with redux store

Using Redux Actions to navigate


Since we are done with the Redux integration, let's test if the integration actually works and
if we are able to navigate by dispatching an action.

Let's refactor our Home.page.js file to dispatch an action on About button click instead of
using this.props.navigation.

Home.page.js

import {connect} from 'react-redux';


import {NavigationActions} from 'react-navigation';

class HomePage extends Component {


render () {
return <Home onAboutPress={this.props.onAboutPress}/>;
}
}

const mapDispatchToProps = (dispatch) => ({


onAboutPress: () => {
dispatch(NavigationActions.navigate({routeName: 'about'}));
}
});

export default connect(null, mapDispatchToProps)(HomePage);

And voila! it works like a charm.

As you can see above, we are dispatching an action to navigate to about page. You might
be wondering what's NavigationActions.navigate . Well, it's just an action creator which
dispatches an action with type "Navigation/NAVIGATE".

Passing route params


If you need to pass some parameters to the next route, you could do so using the params
key. Example:

115
Integrating with redux store

dispatch(NavigationActions.navigate({routeName: 'about', params: {someKey: 'someValue'


}}));

You can access the passed params in the page using this.props.navigation.state.params
inside the page file.

About.page.js

import result from 'lodash/result';

class AboutPage extends Component {


render () {
const navigatingFrom = result(this.props, 'navigation.state.params.navigatingFrom'
, '');
return (
<View style={styles.container}>
<Text>About View </Text>
{navigatingFrom ? <Text>Navigating from: {navigatingFrom} </Text> : null}
</View>
);
}
}

Gotchas
Here are some gotchas which you might face:

You only have access to the routes defined in the current stack while navigating. If you
try to navigate to a sibling stack from a nested page, you will face an error. To achieve
this, you would need to goBack to the index screen of the current stack and then go to
the screen where you want to navigate or use reset action creator.
If you wish to add analytics/screen tracking, use redux-middleware defined here.
Otherwise, you could also use the redux-ga-screen-tracker npm module, which does
Google analytics screen tracking automatically.

Note: navigation-actions provides us with a lot of other action-creators as well such as


back, reset, etc. Please have a look here to know all of them.

116
Integrating with redux store

The code till here can be found on the branch chapter/10/10.2

117
File Structure for routes

File Structure for routes


As we add more and more routes, our router file tends to become huge and unmanageable.
The index.js file will be containing styles defining the spacing of tabs, fontSize, etc and at
the same time defining types of nested stacks and their screens.

This file can become unmanageable in a very short time. Hence we split the files into
multiple files, each defining its own stack and importing styles from a separate file.

Splitting your index.js file to multiple router files


The current index.js looks something like this:

routes/index.js

118
File Structure for routes

const AboutRoutes = TabNavigator({


aboutApp: {
screen: AboutApp,
navigationOptions: {
title: 'About the App'
}
},
aboutDevs: {
screen: AboutDevs,
navigationOptions: {
title: 'About the Creators'
}
}
}, {
tabBarOptions: {
upperCaseLabel: false,
showIcon: false
},
swipeEnabled: true,
...,
animationEnabled: true
});

const Router = StackNavigator({


home: {screen: HomePage,
navigationOptions: {
title: 'Start taking notes',
}
},
about: {
screen: AboutRoutes
}
});

export default Router;

As we can see, the current file defines two stacks, AboutRoutes, and the main Router.

Let's create a new file which will contain just the screens required for About page and import
it in the routes/index.js file.

routes/index.js

119
File Structure for routes

import AboutRoutes from './about.routes.js';

const Router = StackNavigator({


home: {screen: HomePage,
navigationOptions: {
title: 'Start taking notes',
}
},
about: {
screen: AboutRoutes
}
});

export default Router;

routes/about.routes.js

export default TabNavigator({


aboutApp: {
screen: AboutApp,
navigationOptions: {
title: 'About the App'
}
},
aboutDevs: {
screen: AboutDevs,
navigationOptions: {
title: 'About the Creators'
}
}
}, {
tabBarOptions: {
upperCaseLabel: false,
showIcon: false
},
swipeEnabled: true,
...,
animationEnabled: true
});

Pretty neat huh?

We can further modularize it such that all the navigation config data comes from a different
file. This way, there will be a single file containing navigation config for all the routes. We
can even reuse some of the configs.

Taking out config data from routes file

120
File Structure for routes

Let's take out the configuration file from AboutRoutes.

config/routes.config.js

export const aboutRoutesConfig = {


tabBarOptions: {
upperCaseLabel: false,
showIcon: false
},
swipeEnabled: true,
animationEnabled: true
};

routes/about.routes.js

import {aboutRoutesConfig} from '../config/router.config';

export default TabNavigator({


aboutApp: {
screen: AboutApp,
navigationOptions: {
title: 'About the App'
}
},
aboutDevs: {
screen: AboutDevs,
navigationOptions: {
title: 'About the Creators'
}
}
}, aboutRoutesConfig);

The router file looks much cleaner now, defining just the routes and their title. In future, if we
need to change the config, we need not go inside each route file and search for its config.

121
File Structure for routes

The code till here can be found on the branch chapter/10/10.3

122
DevOps ⚙

DevOps
Whenever we start a new project, after setting up the boilerplate, best practices suggest that
we set up the continuous deployment so that the team can easily trigger and receive test
builds of the latest app code.

This also makes it easier for the testing team to receive test builds without continuously
bugging the developers.

A few basic requirements for a good DevOps setup can be:

Lint and tests with coverage should run whenever a pull request is raised.
Every time changes are merged to the master branch, we want the CI process to build
the code, run lint and tests, and then build and publish apps to be distributed to the
internal testing team.
Each test build should have the same version and build number for Android and iOS
builds, corresponding to each version of the code.
An email should be sent out to all testers whenever a new build is available.
Developers should also have the option to manually build on their local machines
quickly.
Ability to pass environment variables to the scripts so that we can generate different
builds for different development environments like staging, preprod, and production.

The following chapters would show step by step on how to achieve these.

123
Android Build setup

Android build scripts/setup


In Android, builds are done via Gradle.

These are a few pre-requisites to build a release apk.

BUILD_NAME - The name that will be used by testers to identify the build, for example:
'1.1.1', '1.0-alpha', etc.
BUILD_NUMBER - A unique integer number identifying the build. This is used by
Android to identify which build is the updated build. This should be an integer number,
for example, 1, 111, 111, etc.
ANDROID_APP_ID - This is the unique app identifier which is used to identify the app
uniquely in the Google Play Store or can be used to identify if the build is dev, preprod
or prod. These app ids may look like this: com.app.notetaker-dev, com.app.notetaker-
alpha.
ANDROID_KEYSTORE_FILE - This is the keystore file used to sign the app.
ANDROID_KEYSTORE_PASSWORD - This is the keystore password used while
creating the keystore.
ANDROID_KEY_ALIAS - This is the alias used to create the keystore.
ANDROID_KEY_PASSWORD - This is the password set for the key.

Release variants
Ideally, every app has three release variants just like a typical backend application:

Dev build - The app which connects to the staging/dev backend environment. This can
also include additional libraries like TestFairy.
Preprod build - The app which points to the preprod backend environment. This is
usually very similar, if not identical to the production app.
Prod build - The final apk which should be released to the Play Store.

Hence, we would need three different key stores for three different variants.

Let's get started


The first step is to create a new keystore file.

Use the following command to create a keystore.

keytool -genkey -v -keystore dev_release.keystore -alias dev-alias -keyalg RSA -keysiz


e 2048 -validity 10000

124
Android Build setup

You would be prompted to enter ANDROID_KEYSTORE_PASSWORD,


ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD and a few other details.

Note these down somewhere and keep the keystore file safe.

The Android documentation has the following warning:

Note about saving the keystore:

Once you publish the app on the Play Store, you will need to republish your app under
a different package name (losing all downloads and ratings) if you want to change the
signing key at any point. So backup your keystore and don't forget the passwords.

For the build to work the keystore file should be placed at


android/app/dev_release.keystore .

Make sure the keystore file is git ignored so that you don't check the file into git

Now, create the following script.

scripts/android/builder.sh

#!/bin/bash

set -e
cur_dir=`dirname $0`

echo "BUILDING ANDROID";


cd $cur_dir/../../android &&
./gradlew clean assembleRelease -PBUILD_NAME=$BUILD_NAME -PBUILD_NUMBER=$BUILD_NUMBER
-PANDROID_APP_ID=$ANDROID_APP_ID -PMYAPP_RELEASE_STORE_FILE=$ANDROID_KEYSTORE_FILE -PM
YAPP_RELEASE_KEY_ALIAS=$ANDROID_KEY_ALIAS -PMYAPP_RELEASE_STORE_PASSWORD=$ANDROID_KEYS
TORE_PASSWORD -PMYAPP_RELEASE_KEY_PASSWORD=$ANDROID_KEY_PASSWORD && cd ..

echo "APK will be present at android/app/build/outputs/apk/app-release.apk"

Apart from this script, we would also need to modify:

android/app/build.gradle

125
Android Build setup

...
def enableProguardInReleaseBuilds = false
...
...
def appID = System.getenv("ANDROID_APP_ID") ?: "com.notetaker"
def vCode = System.getenv("BUILD_NUMBER") ?: "0"
def vName = System.getenv("BUILD_NAME") ?: "1.0.local"

android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId appID
minSdkVersion 16
targetSdkVersion 22
versionCode Integer.parseInt(vCode)
versionName vName
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
...
...
...
signingConfigs {
release {
if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}
}
buildTypes {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rule
s.pro"
signingConfig signingConfigs.release
}
}
...
...
...

The above script tells Gradle to

1. clean
2. build a release apk

126
Android Build setup

Also, all the important parameters are passed via environment variables. This ensures that
we can:

1. Build the apk manually by running

BUILD_NAME=1.1.1 BUILD_NUMBER=11 ANDROID_APP_ID='com.test.appid' ANDROID_KEYSTORE_


FILE='dev_release.keystore' ANDROID_KEY_ALIAS='dev-alias' ANDROID_KEYSTORE_PASSWOR
D=<PASSOWORD> ANDROID_KEY_PASSWORD=<PASSWORD> sh ./scripts/android/builder.sh

2. Setup the environment variables in CI platform so that the CI can build the apk without
manual intervention. Keep in mind that typically every CI provides a unique build
number that you can pass to the script for BUILD_NUMBER.

Woot! Its that simple to build a release apk for Android.

The built apk can be found at: android/app/build/outputs/apk/app-release.apk

Windows users
If you are running Windows 10 64bit or higher you can enable Ubuntu bash shell on your
systems and gain access to the full bash command line and run the script there. More on
that here: https://www.howtogeek.com/249966/how-to-install-and-use-the-linux-bash-shell-
on-windows-10/

Alternatively, you could install Cygwin on your system and run the scripts mentioned.

The code till here can be found on the branch chapter/11/11.1

127
iOS Build setup

iOS build scripts/setup


iOS builds via command line are much more complex as compared to Android.

Note: Builds work only for Mac users. Since Apple requires that all the builds be
made on Xcode itself, iOS apps can only be built on a Mac machine.

These are the few pre-requisites to build a release ipa file.

BUILD_NAME - The name that will be used by testers to identify the build, for example:
'1.1.1', '1.0-alpha', etc.
BUILD_NUMBER - A unique integer number identifying the build. This is used by iOS to
identify which build is the updated build. This should be an integer number. For
example: 1, 111, 111, etc.
IOS_APP_ID - This is the unique app identifier which is used to identify the app
uniquely in the App Store or it can be used to identify if the build is dev, preprod or prod.
App ids may look like this: com.app.notetaker-dev, com.app.notetaker-alpha.
IOS_CERTIFICATE - This is the certificate file used to sign the app.
IOS_CERTIFICATE_KEY - This is the password used while creating the certificate.
IOS_PROVISION_PROFILE - This is the provision profile needed to build the app. This
file mentions the capabilities/devices that are allowed to run the app.
IOS_EXPORT_OPTIONS_PLIST - This is the options file needed to specify parameters
for the build.
IOS_SCHEME - The scheme which should be used to build the IPA. Typically, we will
have different schemes per environment. For example, we can have a local, a preprod
and a production scheme.
IOS_CONFIGURATION - This is the setting which specifies if the build is DEBUG or
RELEASE.
PROJECT_NAME - This is the name of the project. For example, if your project name
inside ios folder says SampleProject.xcworkspace or SampleProject.xcodeproj, then
PROJECT_NAME=SampleProject .

Release variants
Ideally, every app has three release variants just like a typical backend application:

Dev build - The app which connects to the staging/dev backend environment. This can
also have additional libraries like TestFairy.
Pre-Prod build - The app which points to the preprod backend environment. This is
usually very similar, if not identical to the production app.

128
iOS Build setup

Prod build - The final IPA which should be released to the App Store.

Hence, we would need three different schemes for three different variants.

A typical build process in iOS would have the following steps:

1. Getting the certificates and provisioning profiles from the Apple developer account.
2. Adding the certificate to the default keychain and placing the provisioning profile at the
location ~/Library/MobileDevice/Provisioning\ Profiles/
3. Archiving the project - Think of it as an executable zip of the project that can be run
using Xcode.
4. Exporting the IPA - Think of it as exporting the archive to a format recognized by an
iPhone.

Let's get started


1. Place the provisioning profile at scripts/ios/profile/XYZ.mobileprovision
2. Place the certificate at scripts/ios/certs/ABC.p12
3. Place an exportOptions file at scripts/ios/exportOptions/exportOptions-dev.plist
Typically, an exportOptions file looks like this :

scripts/ios/exportOptions/exportOptions-dev.plist

129
iOS Build setup

<?xml version="1.0" encoding="UTF-8"?>


<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/P
ropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>compileBitcode</key>
<false/>
<key>method</key>
<string>enterprise</string>
<key>teamID</key>
<string>ABC1234DA</string>
<key>uploadBitcode</key>
<true/>
<key>uploadSymbols</key>
<true/>
<key>manifest</key>
<dict>
<key>appURL</key>
<string>null</string>
<key>displayImageURL</key>
<string>null</string>
<key>fullSizeImageURL</key>
<string>null</string>
</dict>
</dict>
</plist>

Make sure you put all the above files in the gitignore.

4. Create the script: The below script will create a new keychain ios-build and will store
the certificate in the keychain. Also, it will make ios-build the default keychain so that
Xcode picks up the certificate from it. Then it will copy the provisioning profile to the
correct directory so that Xcode can pick it up.

scripts/ios/keychain.sh

130
iOS Build setup

#!/bin/bash

set -e
cur_dir=`dirname $0`

#Check if ios-build keychain exists


export keychainCount=`security list-keychains | grep -E 'ios-build' -c`

if [ $keychainCount == 0 ] ; then
echo "Create ios-build keychain"
# Create a custom keychain
security create-keychain -p "ios-build-password" ios-build.keychain
fi
# Add it to the list
security list-keychains -d user -s ios-build.keychain

echo "Making the ios-build keychain default, so xcodebuild will use it for signing"

security default-keychain -s ios-build.keychain

echo "Unlocking the ios-build keychain"


security unlock-keychain -p "ios-build-password" ios-build.keychain

# Set keychain timeout to 1 hour for long builds


# see http://www.egeek.me/2013/02/23/jenkins-and-xcode-user-interaction-is-not-all
owed/
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain

echo "Importing $IOS_CERTIFICATE to keychain"


security import $cur_dir/certs/$IOS_CERTIFICATE -k ~/Library/Keychains/ios-build.k
eychain -P $IOS_CERTIFICATE_KEY -T "/usr/bin/codesign" -A

#Mac OS Sierra https://stackoverflow.com/questions/39868578/security-codesign-in-s


ierra-keychain-ignores-access-control-settings-and-ui-p
security set-key-partition-list -S apple-tool:,apple: -s -k "ios-build-password" i
os-build.keychain

# Put the provisioning profile in place


echo "Copying $IOS_PROVISION_PROFILE in place"
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp "$cur_dir/profile/$IOS_PROVISION_PROFILE" ~/Library/MobileDevice/Provisioning\
Profiles/

5. Create the script that does the build. This script will run the xcodebuild to first archive
the project. Then it will generate the IPA file which can be used for installing the app
onto an iPhone.

scripts/ios/builder.sh

131
iOS Build setup

#!/bin/bash

set -e
cur_dir=`dirname $0`

WORKING_DIR=`pwd`;
cd $cur_dir/../../ios
echo "Setting version to ${BUILD_NUMBER}, ${BUILD_NAME}"
xcrun agvtool new-version -all ${BUILD_NUMBER}
xcrun agvtool new-marketing-version ${BUILD_NAME}
cd $WORKING_DIR

echo "Archiving the project"


xcodebuild clean archive PRODUCT_BUNDLE_IDENTIFIER=${IOS_APP_ID} -project $cur_dir
/../../ios/${PROJECT_NAME}.xcodeproj -scheme $IOS_SCHEME -configuration $IOS_CONFI
GURATION -derivedDataPath $cur_dir/../../ios/build -archivePath $cur_dir/../../ios
/build/Products/${PROJECT_NAME}.xcarchive

# or if you are not using xcodeproj and are using xcworkspace to build.. use the b
elow code:

# echo "Archiving the project"


# xcodebuild clean archive PRODUCT_BUNDLE_IDENTIFIER=${IOS_APP_ID} -workspace $cur
_dir/../../ios/${PROJECT_NAME}.xcworkspace -scheme $IOS_SCHEME -configuration $IOS
_CONFIGURATION -derivedDataPath $cur_dir/../../ios/build -archivePath $cur_dir/../
../ios/build/Products/${PROJECT_NAME}.xcarchive

#SIGN
# Issue : "No applicable devices found."
# Fix: https://stackoverflow.com/questions/39634404/xcodebuild-exportarchive-no-ap
plicable-devices-found
unset GEM_HOME
unset GEM_PATH

echo "Export archive to create IPA file using $IOS_EXPORT_OPTIONS_PLIST"


xcodebuild -exportArchive -archivePath $cur_dir/../../ios/build/Products/${PROJECT
_NAME}.xcarchive -exportOptionsPlist $cur_dir/../../scripts/ios/exportOptions/$IOS
_EXPORT_OPTIONS_PLIST -exportPath $cur_dir/../../ios/build/Products/IPA

echo "IPA will be found at $cur_dir/../../ios/build/Products/IPA/$IOS_SCHEME.ipa"

Executing the scripts


1. Run the keychain.sh to setup the certificate and provision profile like this:
IOS_CERTIFICATE='ABC.p12' IOS_CERTIFICATE_KEY='PASSWORD'
IOS_PROVISION_PROFILE='ABC.mobileprovision' sh scripts/ios/keychain.sh

2. Now, run the build script to build the ipa file like this: PROJECT_NAME='NoteTaker'
IOS_APP_ID='com.notetaker.app.ios' BUILD_NUMBER=11 BUILD_NAME=1.1.1 IOS_SCHEME='local'
IOS_CONFIGURATION='RELEASE' IOS_EXPORT_OPTIONS_PLIST='exportOptions-dev.plist' sh

132
iOS Build setup

./scripts/ios/builder.sh

The build should take a couple of minutes and you can find the final ipa file at
ios/build/Products/IPA/

The code till here can be found on the branch chapter/11/11.2

133
SVG Icons using react-native-vector-icons

Icons in react-native
Those who come from a web development background know the power of SVG icons. We
prefer using SVGs (for icons, images) because of the following reasons:

They are resolution independent (scalable).


They provide us more flexibility in changing color.
They are smaller in size as compared to images.
They are written in XML syntax so they can be inserted directly into HTML without
worrying about bundling assets.

That was on the web. Does react-native support SVGs?


Unfortunately, rendering SVGs in native is not as simple as it is in HTML/Web, where you
can use SVG as an image or copy paste SVG content inside your HTML. Unlike the web,
react-native doesn't support SVG out of the box. Though there are some plugins which let
you render SVG, they do not support all SVG elements and are not very easy to use.

How do you get the power of SVG in a native environment?


Let me introduce you to this amazing library called react-native-vector-icons . It comes
bundled with a bunch of icon sets (default is FontAwesome ).

All the fonts are scalable and you can style them just like SVG.
It returns a React component which accepts name, etc. as prop. After integration, the
usage will be as simple as <Icon name="rocket" size={30} color="#900" />
You can pass custom style.
It also supports a couple of other components which might be useful. Example: button
with an icon ( <Icon.Button /> )
You can also create your own icon set if you want to use custom icons.
Installation is a 2 step process if you wish to use the icons provided by FontAwesome or
other similar font libraries. ( yarn add and react-native link )
If you wish to use custom fonts (made in SVG), please read the next chapter.

134
Custom Icon set

Creating custom icon set


react-native-vector-icons supports using custom icon sets if you do not want to use the

icons which come bundled with it or if you want to add your own icons. It supports Fontello
and IcoMoon to create custom fonts. We used IcoMoon to convert our SVGs to a config
which is readable by the library.

Setting up the framework


Install the npm module using npm or Yarn.

npm install --save react-native-vector-icons

or

yarn add react-native-vector-icons

Configuring your project to support custom icon sets.


We will use IcoMoon to support custom icons/fonts. Icomoon is a free and open source
application which lets you convert a set of SVG icons to fonts. It is a front end only app and
does not upload your icons anywhere, so there will be no privacy concerns!

The steps for the configuration are as follows:

Create a resources folder where we will keep our custom font file (.ttf).

Do react-native link react-native-vector-icons . This will set up the vector icons


framework for you.

That's it, you are done with the setup. Now we need to get the TTF file and place it in the
resources/fonts folder that we just created.

How to generate .ttf fonts using IcoMoon:


1. Open the IcoMoon application.

2. Remove the current set (if there is one) and create a new empty set and give your
preferred name (remember to give the same name everywhere).

135
Custom Icon set

3. Drag and drop your SVG files onto the tool.

4. Select the files which you want to export. Select all if you want to export all the icons.

136
Custom Icon set

5. After the selection, click Generate Font. This will download a zip file to your system.

6. The zip file will contain a selection.json file and a fonts folder containing a .ttf file. We
only need these two files to use fonts in react-native.

7. Put the font file (.ttf) in the resources/fonts folder and add the following script to the
package.json:

137
Custom Icon set

"rnpm": {

"assets": [
"resources/fonts"
]
}

This script will copy the font files to both Android and iOS folders. After this, whenever
we want to update the fonts, we will do react-native link react-native-vector-icons
and the fonts will be copied/updated automatically to both Android and iOS projects.

8. Put the JSON file (selection.json) in your app and create a file called CustomIcon.js.
Import the selection.json in CustomIcon.js.

import {createIconSetFromIcoMoon} from 'react-native-vector-icons';


import icoMoonConfig from './selection.json';
export default createIconSetFromIcoMoon(icoMoonConfig);

9. That's it! To use a font simply import the file as a React component and pass the icon
name and size (optional) or even style.

import CustomIcon from './components/CustomIcon.js'

<CustomIcon name='android' /> //To use the icon


<CustomIcon name='android' size={25} /> // To pass size
<CustomIcon name='android' style={styles.androidIcon} /> // To pass custom tyle

Changing file names of the font file


The default name of the font file is icomoon.ttf . If you want to give it a different name, go to
Preferences after step 5 and change the name there before downloading. Also, make sure
that if you change the font file name, give the same name to the set as well (by default it is
"Untitled Set").

It is not recommended to change the filename of .ttf font file after the setup/native linking.
The filename gets written in project.pbxproj and Info.plist and the file gets copied to
android/app/src/main/assets/fonts/ once you run the link command. If you wish to change

the filename, you would need to take care of changing the above 2 files as well, and
removing the unused icon from the android folder which might cause problems if not done
properly.

How do I add/delete icons from the font file (.ttf)

138
Custom Icon set

You can easily change/delete the contents of the font file. The tool only needs the
selection.json file, which defines the font configuration. The steps for the same are as

follows:

1. Open IcoMoon App

2. Upload the current selection.json file.

3. Edit/delete the icon using the tools on top. (Adding an icon is the same as step 3
mentioned above)

4. After the editing is complete, generate a new font file by following the steps 5 and 6
mentioned above. Once you have the new font file and the new selection.json file, place
them in their appropriate locations in the app and do react-native link react-native-
vector-icons .

That's how you change the icons. Pretty neat huh?

This will let us convert any SVG image to a font which is scalable, platform
independent and easy to style. What else can you ask for, right?

The code till here can be found on the branch chapter/12

139
Custom Icon set

140
Internationalization

Internationalization
For application developers, internationalizing an application means abstracting all of the
strings and other locale-specific things like date, currency etc.

We will create a file called en.js and hi.js containing all the strings in a flat JSON
format. Our presentational components will import the strings from one of these files
depending on the current language. Both the language files will contain the same keys at
any point in time.

Framework for Internationalization


We will be using react-native-i18n. It integrates i18n.js with React Native and uses the user
preferred locale as default. I18n.js is a very famous vanilla JS library which supports
features like date/time localization, number localization, locale fallback, etc. You can find
more info about i18n.js here.

Note that the only feature this module gives us is setting the device locale as default app
locale. If you do not need this feature, you may skip installing the native module and use the
i18n-js module instead.

Setting up the framework


1. Let's start by installing the npm module.
yarn add react-native-i18n

2. After this is done, we would need to link it to our app using

react-native link react-native-i18n

3. Create language js files containing language strings in flat JSON format. We will follow
the convention of <PAGENAME>_contentTypeInCameCase . The reason for this convention is
that it will be easier for the testers/devs to know if a translation is missing considering
we will be having guess mode enabled (explained later).

Example: config/language/en.js

141
Internationalization

export default {
HOME_noteTitle: 'Note Title',
HOME_pleaseTypeYourNote: 'Please type your note below',
HOME_startTakingNotes: 'Start taking notes',
HOME_save: 'Save',
HOME_characters: 'chacters',
ABOUT_us: 'About Us',
ABOUT_theApp: 'About the app',
ABOUT_theCreators: 'About the Creators',
ABOUT_theAppDesc: 'About the app',
ABOUT_theCreatorsDesc: 'About the Creators',
};

4. Create a utility file which will export a translate function and some other utility functions.

Refer to the comments to know what each line/function does

utils/language.utils.js

142
Internationalization

import I18n from 'react-native-i18n'; // You can import i18n-js as well if you do
n't want the app to set default locale from the device locale.
import en from '../config/language/en';
import hi from '../config/language/hi';

I18n.fallbacks = true; // If an English translation is not available in en.js, it


will look inside hi.js
I18n.missingBehaviour = 'guess'; // It will convert HOME_noteTitle to "HOME note
title" if the value of HOME_noteTitle doesn't exist in any of the translation file
s.
I18n.defaultLocale = 'en'; // If the current locale in device is not en or hi
I18n.locale = 'en'; // If we do not want the framework to use the phone's locale
by default

I18n.translations = {
hi,
en
};

export const setLocale = (locale) => {


I18n.locale = locale;
};

export const getCurrentLocale = () => I18n.locale; // It will be used to define i


ntial language state in reducer.

/* translateHeaderText:
screenProps => coming from react-navigation (defined in app.container.js)
langKey => will be passed from the routes file depending on the screen.(We will
explain the usage later int the coming topics)
*/
export const translateHeaderText = (langKey) => ({screenProps}) => {
const title = I18n.translate(langKey, screenProps.language);
return {title};
};

export default I18n.translate.bind(I18n);

5. Setup is done. Now let's import the translations from the utility file instead of hardcoding
it in the components.

Instead of

<Text style={styles.characterCount}>{text.length} characters</Text>

we will write

143
Internationalization

<Text style={styles.characterCount}>{text.length} {translate('HOME_characters')}<


/Text>

After following the above 5 steps, we will have an internationalization framework setup and
all our strings coming from a single language config file.

Let's change the defaultLocale of the app from en to hi in language.utils.js and see if
our framework setup works.

TADA! Our setup works like a charm and we can see everything in Hindi

144
Internationalization

The code till here can be found on the branch chapter/13/13.1

145
Adding language toggle feature

Adding language toggle feature


Our framework set up for internationalization is done, but an important part still remains,
which is implementing a change language feature. Every internationalization enabled app
will have this feature. Let's go ahead and build the same for our NoteTaker app.

Let's start by adding a toggle button in our Home component. The button should show the
current language and should toggle the language onPress.

For this, we would need:

Current language from Redux store.


Ability to change the language on action dispatch.

For now, we will create a button in Home.component.js which will accept currentLanguage
and toggleLanguage functions as props.

Home.component.js

const {..., currentLanguage, toggleLanguage} = this.props;


return (
...
<Touchable style={styles.changeLanguage} onPress={toggleLanguage}>
<Text style={styles.changeLanguageText}>{currentLanguage}</Text>
</Touchable>
...
)

Integration with Redux store


1. Create a userPreference reducer which will have a key called language. The
initialState of language should come from language.utils. Our reducer would look
something like this:

reducers/userPreferences.reducer.js

146
Adding language toggle feature

import {CHANGE_LANGUAGE} from '../actions/index.actions';


import {getCurrentLocale} from '../../utils/language.utils';

const initialState = {language: getCurrentLocale()};

const userPreferences = (state = initialState, action) => {


switch (action.type) {
case CHANGE_LANGUAGE: {
return {...state, language: action.payload};
}
default:
return state;
}
};

export default userPreferences;

2. We need to make sure that the language in Redux store is always in sync with the
language in i18n module. In other words, we need an action creator which changes
the language in both i18n module and Redux store.

We will use redux-thunk middleware for this. You can use redux-saga to achieve this as
well. To know more about thunks, go here.

redux/thunks/index.thunks.js

import {changeLanguage} from '../actions/index.actions.js';


import {setLocale} from '../../utils/language.utils';

export const setCurrentLanguage = (lang) => (dispatch) => {


setLocale(lang);
dispatch(changeLanguage(lang));
};

export const toggleLanguage = () => (dispatch, getState) => {


const currentLanguage = getState().userPreferences.language;
if (currentLanguage === 'en') {
dispatch(setCurrentLanguage('hi'));
} else {
dispatch(setCurrentLanguage('en'));
}
};

3. After defining the setCurrentLanguage and toggleLanguage , we can dispatch them on


onPress of our toggle button. Let's pass toggleLanguage and currentLanguage to the
Home Component.

Home.page.js

147
Adding language toggle feature

import {toggleLanguage} from '../redux/thunks/index.thunks';

class HomePage extends Component {


render () {
const {..., toggleLanguage, currentLanguage} = this.props;
return (
<Home {...} currentLanguage={currentLanguage} toggleLanguage={toggleLanguag
e}/>
);
}
}
const mapStateToProps = (state) => ({
...
currentLanguage: state.userPreferences.language
});
const mapDispatchToProps = (dispatch) => ({
...
toggleLanguage: () => dispatch(toggleLanguage())
});

That's all! You should now be able to see a toggle button on the Home page showing the
current language and be able to toggle the language on press.

148
Adding language toggle feature

Don't worry about the header text not being changed. We will fix it in the coming chapter.

The code till here can be found on the branch chapter/13/13.2

149
Integration with react-navigation

Integration with react-navigation


Our framework setup is completed, so is the toggle language feature. Everything seems to
be working, except for one thing. The header text is not getting re-rendered on language
change. Have a look at the screenshot below.

The current language is Hindi . Everything changes in the UI except the header.

But why is this happening?

We are using translate function in the routes file. Right?

Answer: Yes we are, but how will the router know that it has to re-render?

Luckily, react-navigation provides us with a feature to pass screenProps . ScreenProps will


be passed to each navigator instance's navigationConfig. We can pass screenProps as a
prop to the Router component in our App.container .

App.container.js

150
Integration with react-navigation

However, change in screenProps will not cause re-rendering of Router. Have a look at the
issue here to know why.

To fix this, we would use a function instead of an object for our navigationConfig (have a look
at Header Configuration topic here). ScreenProps are passed to this function and we can
use them to get the currentLanguage and then translation.

Instead of

navigationOptions: {
title: translate('HOME_startTakingNotes')
}

we will do

navigationOptions: ({screenProps}) => {


const title = I18n.translate('HOME_startTakingNotes', screenProps.language);
return {title};
};

Doing this will cause the header text to change dynamically based on the currentLanguage.
Let's create a utility for this so that we don't have to rewrite this function.

language.utils.js

export const translateHeaderText = (langKey) => ({screenProps}) => {


const title = I18n.translate(langKey, screenProps.language);
return {title};
};

Now let's use this utility in all our routes file. Example usage:

routes/index.js

navigationOptions: translateHeaderText('HOME_startTakingNotes')

Our app now supports internationalization without any bugs.

151
Integration with react-navigation

The code till here can be found on the branch chapter/13/13.3

152
Custom Native Modules

Custom Native Modules


The Problem
Although React Native has extensive amounts of native module libraries available, thanks to
the huge open source community around it, sometimes there are specific requirements
that arise during the course of a project which require native platform API access not
provided by React Native. As an example let's say you are working with a client and they
have a custom SDK for authentication. The client has SDKs for iOS, Android, and Web.
Although you are building a native app for Android/iOS on top of a Web technology (React),
the irony is that this task suddenly becomes very difficult.

Solution
The beauty of React Native is that it is designed to be a bridge between native code and
web technologies. Thus, all you need to do is simply integrate the native iOS and Android
SDKs into the respective native projects and expose the methods and variables via a
React Native module called: NativeModules .

The module which talks to the native libs and can be accessed via JS is called Wrapper
module. We will discuss how to create custom wrapper modules that can be used to invoke
native code from Javascript in the following chapters.

On a side note, if the requirement was to integrate a native module that doesn't need to
interact with the Javascript layer then we can safely integrate the native modules in the
respective native projects for iOS and Android and not create a React Native bridge. One
such use case can be crash reporters which need to send a crash report if an app crashes.
Here you only integrate the crash reporter on the native code base of iOS and Android and
not expose any method to the Javascript side. The initialization would also happen when the
native app starts.

Thus, if a functionality is technically supported by the native platform, it can be


supported in React Native.

153
Android Native Modules

Android custom native module


Let's say we want to build a native module that gets you the device name.

Before we continue, remember that if you want to write code for Android, you need to open
the android project in Android Studio. This is because Android Studio is built for Android
development and it will help you resolve all the trivial errors that otherwise would take too
much time to resolve.

Let's get started


1. Open the ./android/ project in Android Studio.

2. Create a Java class file


android/app/src/main/java/com/notetaker/device/DeviceModule.java This is our main

custom native module file. The custom native module class should extend
ReactContextBaseJavaModule . After this, we will have to implement the getName()

method.

The getName method basically contains the name by which the module will be exported
to the JS.

In order to expose a method from a native Java module to Javascript, just write a
method and add @ReactMethod annotation on top of it.

154
Android Native Modules

These methods can be accessed from NativeModules of the react-native package.


See the example below:

android/app/src/main/java/com/notetaker/device/DeviceModule.java

package com.notetaker.device;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class DeviceModule extends ReactContextBaseJavaModule {


//constructor
public DeviceModule(ReactApplicationContext reactContext) {
super(reactContext);
}
//Mandatory function getName that specifies the module name
@Override
public String getName() {
return "Device";
}
//Custom function that we are going to export to JS
@ReactMethod
public void getDeviceName(Callback cb) {
try{
cb.invoke(null, android.os.Build.MODEL);
}catch (Exception e){
cb.invoke(e.toString(), null);
}
}
}

Here we are exporting a method getDeviceName() from native to Javascript. This


method can be accessed in JS via

import {NativeModules} from 'react-native';


NativeModules.Device.getDeviceName((err ,name) => {
console.log(err, name);
});

NativeModules has a key named 'Device'. This is basically the same name we exported
using the method getName . And getDeviceName is exported because of @ReactMethod .

We passed a callback to get the value from the NativeModule.

3. But creating a module file is not enough. Before a native module can be used we need
to register the module. To do this we create another Java class

155
Android Native Modules

android/app/src/main/java/com/notetaker/device/DevicePackage.java

package com.notetaker.device;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class DevicePackage implements ReactPackage {

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContex
t) {
return Collections.emptyList();
}

@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
//We import the module file here
modules.add(new DeviceModule(reactContext));

return modules;
}

// Backward compatibility
public List<Class<? extends JavaScriptModule>> createJSModules() {
return new ArrayList<>();
}
}

This file just imports our module and instantiates it.

4. The last step in the registration process is to instantiate our DevicePackage class.

To do this modify the file

android/app/src/main/java/com/notetaker/MainApplication.java

156
Android Native Modules

...
...
import com.notetaker.device.DevicePackage;
...
...
...

@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
...
...
new DevicePackage() //Add your package here
);
}
};
...
...
...

That's it, let's give it a shot!


In a Javascript file, you can access the module methods using NativeModules.<moduleName>.
<methodName>

app/index.js

import {NativeModules} from 'react-native';


...
...
NativeModules.Device.getDeviceName((err, name) => console.log(err, name));
...
...

Running this on an Android emulator returns

157
Android Native Modules

Woot! That was simple!

The code till here can be found on the branch chapter/16/16.1

Note that if you try to build for iOS now, the build will fail as we have not implemented
the Device module in iOS yet.

158
iOS Native Modules

iOS custom native module


Let's build the same native module for iOS that we built for Android in the last chapter. The
purpose remains the same, the native module should get you the device name set on
an iPhone.

Before we continue, just a reminder that if you want to write code for iOS, please open the
iOS project on Xcode. This is because Xcode is built for iOS development and it will help
you resolve the trivial errors that otherwise would take up too much time.

Let's get started


1. Open the ./ios/ project folder. Open the .xcodeproj file or the .xcworkspace file in
Xcode.

2. Create a header file Device.h by following the steps File -> New -> File -> Header
File and then name the file Device.h and choose the targets. Create a new folder

Device if you like to organize files in a folder like me.

3. Let modify our Device.h file.

ios/Device/Device.h

159
iOS Native Modules

#import <React/RCTBridgeModule.h>

@interface Device : NSObject <RCTBridgeModule>


@end

This is our main custom native modules header file.

4. Now let's create the corresponding implementation file Device.m in the same location.

ios/Device/Device.m

#import "Device.h"

@implementation Device

RCT_EXPORT_MODULE();

@end

5. Similar to Android's getName method, here we have RCT_EXPORT_MODULE() macro.


If no name is explicitly provided, it will take the name of the module. Here the name of
the module is 'Device'. In order to expose a method from native module to
Javascript just write a method inside the RCT_EXPORT_METHOD macro. These
methods can be accessed from NativeModules of the react-native package.

See the example below:

ios/Device/Device.m

160
iOS Native Modules

#import "Device.h"
#import <UIKit/UIKit.h>

@implementation Device

//export the name of the native module as 'Device' since no explicit name is menti
oned
RCT_EXPORT_MODULE();

//exports a method getDeviceName to javascript


RCT_EXPORT_METHOD(getDeviceName:(RCTResponseSenderBlock)callback){
@try{
NSString *deviceName = [[UIDevice currentDevice] name];
callback(@[[NSNull null], deviceName]);
}
@catch(NSException *exception){
callback(@[exception.reason, [NSNull null]]);
}
}

@end

Here we are exporting a method getDeviceName() from native to Javascript. This


method can be accessed in JS via

import {NativeModules} from 'react-native';


NativeModules.Device.getDeviceName((err ,name) => {
console.log(err, name);
});

NativeModules has a key named 'Device'. This is basically the same name exported by
RCT_EXPORT_METHOD.

We passed a callback to get the value from the NativeModule.

That's it, let's give it a shot!


In a Javascript file, you can access the module methods using NativeModules.<moduleName>.
<methodName>

app/index.js

Just add

161
iOS Native Modules

import {NativeModules} from 'react-native';


...
...
NativeModules.Device.getDeviceName((err, name) => console.log(err, name));
...
...

Running this on an iOS simulator returns.

Woot! That was simple!

The code till here can be found on the branch chapter/16/16.2

162
References

References
http://reactkungfu.com/2015/07/why-and-how-to-bind-methods-in-your-react-
component-classes/
https://medium.com/differential/managing-configuration-in-react-native-cd2dfb5e6f7b
http://slides.com/rahulgaba/react-native
https://medium.com/the-react-native-log/comparing-the-performance-between-native-
ios-swift-and-react-native-7b5490d363e2
https://www.youtube.com/watch?v=8qCociUB6aQ
https://github.com/jondot/awesome-react-native
https://blog.expo.io/good-practices-why-you-should-use-javascript-whenever-possible-
with-react-native-26478ec22334
https://blog.joinroot.com/mounting-react-native-components-with-enzyme-and-jsdom/

163
The End

Don't forget to hit ★ if you like our work :)

164

Das könnte Ihnen auch gefallen