Fullstack React Native Devin Abbot, Houssein Djirdeh, Anthony Accomazzo, And Sophia Shoemaker

Devin%20Abbot%2C%20Houssein%20Djirdeh%2C%20Anthony%20Accomazzo%2C%20and%20Sophia%20Shoemaker%20-%20Fullstack%20React%20Native%20

Fullstack.React.Native.The..Guide.to.React.Native

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 552

DownloadFullstack React Native Devin Abbot, Houssein Djirdeh, Anthony Accomazzo, And Sophia Shoemaker
Open PDF In BrowserView PDF
Fullstack React Native
The Complete Guide to React Native

Written by Devin Abbott, Houssein Djirdeh, Anthony Accomazzo, and Sophia
Shoemaker
© 2017 Fullstack.io
All rights reserved. No portion of the book manuscript may be reproduced, stored in a retrieval
system, or transmitted in any form or by any means beyond the number of purchased copies,
except for a single backup or archival copy. The code may be used freely in your projects,
commercial or otherwise.
The authors and publisher have taken care in preparation of this book, but make no expressed
or implied warranty of any kind and assume no responsibility for errors or omissions. No
liability is assumed for incidental or consequential damagers in connection with or arising out
of the use of the information or programs container herein.
Published in San Francisco, California by Fullstack.io.

FULLSTACK .io

Contents
Book Revision . . . . . . . . . . . .
Bug Reports . . . . . . . . . . . . .
Be notified of updates via Twitter
We’d love to hear from you! . . .
Introduction . . . . . . . . . .
About This Book . . . . .
Running Code Examples
Code Blocks and Context
Getting Help . . . . . . . .
Emailing Us . . . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

1
1
1
1

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

1
2
2
3
4
4

Getting Started with React Native
Weather App . . . . . . . . . . .
Starting the project . . . . . . . .
Expo . . . . . . . . . . . . . . . .
Components . . . . . . . . . . . .
Custom components . . . . . . .
Summary . . . . . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

6
6
9
10
17
33
67

React Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Breaking the app into components . . . . . . . . . . . . . . . . . . . . . .
7 step process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Step 2: Build a static version of the app . . . . . . . . . . . . . . . . . . .
Step 3: Determine what should be stateful . . . . . . . . . . . . . . . . . .
Step 4: Determine in which component each piece of state should live
Step 5: Hardcode initial states . . . . . . . . . . . . . . . . . . . . . . . . .
Step 6: Add inverse data flow . . . . . . . . . . . . . . . . . . . . . . . . .
Updating timers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Deleting timers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Adding timing functionality . . . . . . . . . . . . . . . . . . . . . . . . . .
Add start and stop functionality . . . . . . . . . . . . . . . . . . . . . . . .
Methodology review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

68
69
74
76
91
93
95
106
113
119
122
124
132

Core Components, Part 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134

CONTENTS

What are components? . . .
Building an Instagram clone
View . . . . . . . . . . . . . . .
StyleSheet . . . . . . . . . . .
Text . . . . . . . . . . . . . . .
TouchableOpacity . . . . . .
Image . . . . . . . . . . . . . .
ActivityIndicator . . . . . .
FlatList . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

134
134
142
151
153
161
166
173
178

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

190
193
197
204

Core APIs, Part 1 . . . . . . . . . . .
Building a messaging app . . . .
Initializing the project . . . . . .
The app . . . . . . . . . . . . . . .
Network connectivity indicator
The message list . . . . . . . . .
Toolbar . . . . . . . . . . . . . . .
Geolocation . . . . . . . . . . . .
Input Method Editor (IME) . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

216
216
219
220
224
235
256
268
270

Core Components, Part 2
TextInput . . . . . . .
ScrollView . . . . . . .
Modal . . . . . . . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

Core APIs, Part 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
The keyboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
We’re Done! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
Navigation . . . . . . . . . . . . . . . . . . . . . .
Navigation in React Native . . . . . . . . . .
Contact List . . . . . . . . . . . . . . . . . . .
Starting the project . . . . . . . . . . . . . . .
Container and Presentational components .
Contacts . . . . . . . . . . . . . . . . . . . . .
Profile . . . . . . . . . . . . . . . . . . . . . . .
React Navigation . . . . . . . . . . . . . . . .
Stack navigation . . . . . . . . . . . . . . . .
Tab navigation . . . . . . . . . . . . . . . . .
Drawer navigation . . . . . . . . . . . . . . .
Sharing state between screens . . . . . . . .
Deep Linking . . . . . . . . . . . . . . . . . .
Summary . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

311
311
317
322
324
324
328
332
332
344
364
371
378
383

CONTENTS

Animation . . . . . . . . . . . .
Animation challenges . . .
Building a puzzle game . .
App . . . . . . . . . . . . . .
Building the Start screen .
Building the Game screen .
Summary . . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

384
384
386
390
392
415
429

Gestures . . . . . . . . . . . . . .
Building the board . . . . .
Gesture Responder System
PanResponder . . . . . . . .
Draggable component . . .
Finishing the game . . . . .
We’re Done! . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

430
430
442
446
447
461
466

Native Modules . . . . . . . . .
What are native modules?
Building a native module .
Development environment
Initializing the project . . .
iOS . . . . . . . . . . . . . .
Android . . . . . . . . . . .
JavaScript . . . . . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

467
467
469
471
472
477
487
496

Building and publishing . . .
How to read this chapter
Building . . . . . . . . . .
Building with Expo . . . .
iOS . . . . . . . . . . . . .
Android . . . . . . . . . .
Handling Updates . . . .
Summary . . . . . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

504
504
504
505
508
525
532
533

Appendix . . . . . . . . . . . . . . . . .
JavaScript Versions . . . . . . . . .
ES2015 . . . . . . . . . . . . . . . .
ReactElement . . . . . . . . . . . .
Handling Events in React Native
Publishing with Expo . . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

534
534
534
540
541
545

.
.
.
.
.
.
.
.

Changelog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546

CONTENTS

1

Book Revision
Revision 5 - Native modules chapter added to book

Bug Reports
If you’d like to report any bugs, typos, or suggestions just email us at: rn@fullstack.io1 .

Be notified of updates via Twitter
If you’d like to be notified of updates to the book on Twitter, follow @fullstackreact2

We’d love to hear from you!
Did you like the book? Did you find it helpful? We’d love to add your face to our list of testimonials
on the website! Email us at: rn@fullstack.io3 .

1 mailto:rn@fullstack.io?Subject=Fullstack%20React%20Native%20book%20feedback
2 https://twitter.com/fullstackreact
3 mailto:rn@fullstack.io?Subject=React%20Native%20testimonial

Introduction
One of the major problems that teams face when writing native mobile applications is becoming
familiar with all the different technologies. iOS and Android - the two dominant mobile platforms
- support different languages. For iOS, Apple supports the languages Swift4 and Objective-C5 . For
Android, Google supports the languages Java6 and Kotlin7 .
And the differences don’t end there. These platforms have different toolchains. And they have
different interfaces for the device’s core functionality. Developers have to learn each platform’s
procedure for things like accessing the camera or checking network connectivity.
One trend is to write mobile apps that are powered by WebViews. These types of apps have minimal
native code. Instead, the interface is a web browser running an app written in HTML, CSS, and JS.
This web app can use the native wrapper to access features on the device, like the camera roll.
Tools like Cordova8 enable developers to write these hybrid apps. The advantage is that developers
can write apps that run on multiple platforms. Instead of learning iOS and Android specifics, they
can use HTML, CSS, and JS to write a “universal” app.
The disadvantage, though, is that it’s hard to make these apps look and feel like real native
applications. And users can tell.
While universal WebView-powered apps were built with the idea of build once, run anywhere, React
Native was built with the goal of learn once, write anywhere.
React is a JavaScript framework for building rich, interactive web applications. With React
Native, we can build native mobile applications for multiple platforms using JavaScript and React.
Importantly, the interfaces we build are translated into native views. React Native apps are not
composed of WebViews.
We’ll be able to share a lot of the code we write between iOS and Android. And React Native makes
it easy to write code specific to each platform when the need arises. We get to use one language
(JavaScript), one framework (React), one styling engine, and one toolchain to write apps for both
platforms. Learn once, write anywhere.
At its core, React Native is composed of React components. We’ll dig deep into components
throughout this book, but here’s an example of what a React component looks like:

4 https://developer.apple.com/swift/
5 https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/

Introduction.html
6 https://docs.oracle.com/javase/8/docs/technotes/guides/language/index.html
7 https://developer.android.com/kotlin/index.html
8 https://cordova.apache.org/

Introduction

2

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class StyledText extends React.Component {
render() {
return (
{content}
);
}
}
const styles = StyleSheet.create({
text: {
color: 'red',
fontWeight: 'bold',
},
});

React Native works. It is currently being used in production at Facebook, Instagram, Airbnb, and
thousands of other companies.

About This Book
This book aims to be an extensive React Native resource. By the time you’re done reading this book,
you (and your team) will have everything you need to build reliable React Native applications.
React Native is rich and feature-filled, but that also means it can be tricky to understand all of its
parts. In this book, we’ll walk through everything, such as installing its tools, writing components,
navigating between screens, and integrating native modules.
But before we dig in, there are a few guidelines we want to give you in order to get the most out of
this book. Specifically:
• how to approach the code examples and
• how to get help if something goes wrong

Running Code Examples
This book comes with a library of runnable code examples. The code is available to download from
the same place where you downloaded this book.
We use yarn9 to run every example in this book. This means you can type the following commands
to run any example:
9 https://yarnpkg.com/en/

Introduction

3

• yarn start will start the React Native packager and print a QR code. If you’re on an Android
mobile device, scanning this code with the Expo10 app will load the application. For iOS devices,
see the instructions for loading apps onto your phone at the beginning of the first chapter.
• yarn run ios will start the React Native packager and open your app in the iOS Simulator if
you are using a Mac.
• yarn run android will start the React Native packager and open your app on a connected
Android device or emulator.
In the next chapter we’ll explain each of these commands in detail.

Code Blocks and Context
Nearly every code block in this book is pulled from a runnable code example, which you can find
in the sample code. For example, here is a code block pulled from the first chapter:
weather/1/App.js

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class App extends React.Component {
render() {
return (

Open up App.js to start working on your app!
Changes you make will automatically reload.
Shake your phone to open the developer menu.

);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
10 https://expo.io/

Introduction

4

Notice that the header of this code block states the path to the file which contains this code:
code/weather/1/App.js.
This book is written with the expectation that you’ll also be looking at the example code
alongside the chapter. If you ever feel like you’re missing the context for a code example, open up
the full code file using your favorite text editor.
For example, we often need to import libraries to get our code to run. In the early chapters of the
book we show these import statements, because it’s not clear where the libraries are coming from
otherwise. However, the later chapters of the book are more advanced and they focus on key concepts
instead of repeating boilerplate code that was covered earlier in the book. If at any point you’re not
clear on the context, open up the code example on disk.

Getting Help
While we’ve made every effort to be clear, precise, and accurate you may find that when you’re
writing your code you run into a problem.
Generally, there are three types of problems:
• A “bug” in the book (e.g. something is explained incorrectly)
• A “bug” in our code
• A “bug” in your code
If you find an inaccuracy in our description of something, or you feel a concept isn’t clear, email us!
We want to make sure that the book is both accurate and clear.
Similarly, if you’ve found a bug in our code we definitely want to hear about it.
If you’re having trouble getting your own app working (and it isn’t our example code), this case is a
bit harder for us to handle. If you’re still stuck, we’d still love to hear from you, and here some tips
for getting a clear, timely response.

Emailing Us
If you’re emailing us asking for technical help, here’s what we’d like to know:
•
•
•
•
•
•

What revision of the book are you referring to?
What operating system are you on? (e.g. Mac OS X 10.8, Windows 95)
Which chapter and which example project are you on?
What were you trying to accomplish?
What have you tried already?
What output did you expect?

Introduction

5

• What actually happened? (Including relevant log output.)
The absolute best way to get technical support is to send us a short, self-contained example of the
problem. Our preferred way to receive this would be for you to send us an Expo Snack link11 . Snack
is an online code editor that let’s one quickly develop and demo React Native components on the
browser or an actual device without having to set up a brand new project. We’ll explain Expo in
more detail in the next chapter.
When you’ve written down these things, email us at rn@fullstack.io. We look forward to hearing
from you.
11 https://snack.expo.io/

Getting Started with React Native
Weather App
In this chapter we’re going to build a weather application that allows the user to search for any city
and view its current forecast.
With this simple app we’ll cover some essentials of React Native including:
•
•
•
•
•
•

Using core and custom components
Passing data between components
Handling component state
Handling user input
Applying styles to components
Fetching data from a remote API

By the time we’re finished with this chapter, you’ll know how to get started with Create React Native
App and build a basic application with local state management. You’ll have the foundation you need
to build a wide variety of your own React Native apps.
Here’s a screenshot of what our app will look like when it’s done:

7

Getting Started with React Native

The completed app

In this chapter, we’ll build an entire React Native application from scratch. We’ll talk about how to
set up our development environment and how to initialize a new React Native application. We’ll
also learn how Expo allows us to rapidly prototype and preview our application on our mobile
device. After covering some of the basics of React Native, we’ll explore how we compose apps using
components. Components are a powerful paradigm for organizing views and managing dynamic
data.
We’re about to touch on a wide variety of topics, like styling and data management. This chapter
will exhibit how all these topics fit together at a high-level. In subsequent chapters, we’ll dive deep
into the concepts that we touch on here.

Code examples
This book is example-driven. Each chapter is setup as a hands-on tutorial.
We’ll be building apps from the ground up. Included with this book is a download that contains
completed versions of each app as well as each of the versions we develop along the way (the “sample
code.”) If you’re following along, we recommend you use the sample code for copying and pasting
longer examples or debugging unexpected errors. If you’re not following along, you can refer to the
sample code for more context around a given code example.
The structure of the sample code for all the chapters in this book follows this pattern:

Getting Started with React Native

8

├── components/
├── App.js
├── 1/
│
├── components/
│
└── App.js
├── 2/
│
├── components/
│
└── App.js
├── 3/
│
├── components/
│
└── App.js
// ...

At the top-level of the directory is App.js and components/. This is the code for the completed
version of the application. Inside the numbered folders (1/, 2/, 3/) are the different versions of the
app as we build it up throughout the chapter.
Here’s what a code example in this book looks like:
weather/1/App.js

render() {
return (

Open up App.js to start working on your app!
Changes you make will automatically reload.
Shake your phone to open the developer menu.

);
}

Note that the title of the code block contains the path within the sample code where you can find
this example (weather/1/App.js).

JavaScript
This book assumes some JavaScript knowledge.
React Native uses Babel12 as a JavaScript compiler to allow us to develop in the latest version
of JavaScript, ES2016. To understand what we mean by JavaScript versions, you can refer to the
Appendix.
We highlight some of JavaScript’s newer features in the Appendix. We reference the appendix when
relevant.
12 https://babeljs.io/

Getting Started with React Native

9

Starting the project
Create React Native App
To begin, we’re going to use Create React Native App13 (CRNA), a tool that makes it extremely easy
to get started with React Native. If you’ve used Create React App14 before, you’ll notice similarities
here in that no build configuration is required to get up and running. We can install it globally using
yarn15 .

yarn
yarn16 is a node package manager that automates the process of managing all the required

dependencies and packages from npm, an online repository of published JavaScript libraries and
projects, in an application. This is done by defining all our dependencies in a single package.json
file.
npm also has a command line tool, npm, that allows us to maintain and control dependencies.
The tool that we use to build our application, CRNA, does not currently work with the latest
version of npm, npm v5. For this reason, we’ll use yarn throughout the book.

You can refer to the documentation17 for instructions to install yarn for your operating system. The
documentation also explains how to install node18 as well. In order to use CRNA however, Node.js
v6 or later is required.
Here’s a list of some commonly used yarn commands:
• yarn init creates a package.json file and adds it directly to our project.
• yarn installs all the dependencies listed in package.json into a local node_modules folder.
• yarn add new-package will install a specific package to our project as well as include it as a
dependency in package.json. Dependencies are packages needed when we run our code.
• yarn add new-package --dev will install a specific package to our project as well as include it as
a development dependency in package.json. Development dependencies are packages needed
only during the development workflow. They are not needed for running our application in
production.
• yarn global add new-package will install the package globally, rather than locally to a specific
project. This is useful when we need to use a command line tool anywhere on our machine.
13 https://github.com/react-community/create-react-native-app
14 https://github.com/facebookincubator/create-react-app
15 https://yarnpkg.com
16 https://yarnpkg.com
17 https://yarnpkg.com/lang/en/docs/install
18 https://nodejs.org/en/

Getting Started with React Native

10

If we already have an older version of npm than v5 installed, we can use it instead of yarn
and run its equivalent commands19 .

Watchman
Watchman20 is a file watching service that watches files and triggers actions when they are modified.
If you use macOS as your operating system, the Expo and React Native documentation recommend
installing Watchman for better performance. The instructions to install the service can be found
here21 .

Expo
Expo22 is a platform that provides a number of different tools to build fully functional React Native
applications without having to write native code. Beginning a project with CRNA automatically
creates an application that leverages Expo’s development environment.
A benefit of leveraging Expo is that building an application does not require using Xcode for iOS, or
Android Studio for Android. This means that developers can build native iOS applications without
even owning a Mac computer. Using CRNA and Expo is the easiest way to get started with React
Native and is recommended in the React Native documentation23 .
Including Native Code
Using Expo and CRNA isn’t the only way to start a React Native application. If we need to start
a project with the ability to include native code, we’ll need to use the React Native CLIa instead.
With this however, our application will require Xcode and Android Studio for iOS and Android
respectively.
Expo also provides a number of different APIs for device specific properties such as contacts, camera
and video. However, if we need to include a native iOS or Android dependency that is not provided
by Expo, we’ll need to eject from the platform entirely. Ejecting an Expo application means we have
full control of managing our native dependencies, but we would need to use the React Native CLI
from that point on.
We’ll explore how to add native modules onto a React Native project later on this book.
a https://facebook.github.io/react-native/docs/getting-started.html#installing-dependencies

19 https://yarnpkg.com/lang/en/docs/migrating-from-npm/#toc-cli-commands-comparison
20 https://facebook.github.io/watchman/
21 https://facebook.github.io/watchman/docs/install.html#installing-on-os-x-via-homebrew
22 https://expo.io/
23 https://facebook.github.io/react-native/docs/getting-started.html

11

Getting Started with React Native

Previewing the app
To develop and preview apps with Expo, we need to install its client iOS or Android app24 to develop
and run React Native apps on our device.
Android
On your Android mobile device, install the Expo Client on Google Play25 . You can then select Scan
QR code and scan this QR code once you’ve installed the app:

QR Code

If this QR code doesn’t work, we recommend making sure you have the latest version of that Expo
app installed, and that you’re reading the latest edition of this book.
Instead

of

scanning

the

QR

code,

you

can

also

type

the

project

URL,

exp://exp.host/@fullstackio/weather, inside of Expo to load the application.

iOS
You can install the Expo Client via the App Store26 . With an iOS device however, there is no capability
to scan a QR code. This means we’ll first need to build the final app in order to preview it. We can
do this by navigating to the weather/ directory in the sample code folder and running the following
commands:
cd weather
yarn
yarn start

This will start the React Native packager. Pressing s will allow you to send a link to your device
by SMS or e-mail (you’ll need to provide your mobile phone number of email address). Once done,
clicking the link will open the application in the Expo Client.
24 https://github.com/expo/expo
25 https://play.google.com/store/apps/details?id=host.exp.exponent
26 https://itunes.apple.com/us/app/expo-client/id982107779?mt=8

Getting Started with React Native

12

For the app to load on your physical device, you’ll need to make sure that your phone is connected
to the same local network as your computer.
Local Development Tool
In addition to a client app, Expo also provides two local development tools that allow us to
preview, share and publish our projects:
• XDE27 , or the Expo Development Environment, is a desktop app that we can use for
macOS, Windows, or Linux.
• exp28 is a command line interface.
Both options provide a number of different commands and services that we can use to
manage our applications. Instead of using the s hotkey provided by CRNA when we start
the packager to send the link to an iOS device, we can also use exp or XDE instead.
We’ll explore using these local development tools in more detail when we cover deploying
and publishing apps later in this book.

Preparing the app
At this point, you should see the final application load successfully on your device. Play around with
the app for a few minutes to get a feel for it. Try searching for different cities as well a location that
doesn’t even exist.
If you plan on building the application as you read through the chapter, you’ll need to create a brand
new project. Once yarn is installed, let’s run the following command to install Create React Native
App (CRNA) globally:
yarn global add create-react-native-app@1.0.0

The @1.0.0 specifies the version of create-react-native-app to install. It’s important to
lock in version 1.0.0 so that the version on your machine matches that here in the book.

We’ll call our application weather and can use the following command to get started (this command
may take a little while):
create-react-native-app weather --scripts-version 1.14.0
27 https://github.com/expo/xde
28 https://docs.expo.io/versions/latest/workflow/exp-cli

13

Getting Started with React Native

Importantly, we specify the --scripts-version as 1.14.0. We’ll talk about this in a moment.

We’ll then navigate to that directory and boot the app:
cd weather
yarn start

With the pacakger running, we can continue to scan the QR code with an Android device or send a
link directly to our iOS device using the s hotkey. It is important to remember that our device needs
to be connected to the same local network as our computer in order for this to work.
Right now, viewing the app shows our starting point:

Application

Getting Started with React Native

14

Running on a simulator
As we mentioned, using the Expo client app allows us to run our application without using
native tooling (Xcode for iOS, or Android Studio for Android).
However if we happen to have the required build tools we can still run our application in a
virtual device or simulator:
• With a Mac, yarn run ios will start the development server and run the application
in an iOS simulator. We can also start the packager separately with yarn start and
press i to open the simulator.
• With the required Android tools29 , yarn run android will start the application in an
Android emulator. Similarly, pressing a when the React Native packager is running
will also boot up the emulator.
Running an application using an emulator/simulator can be useful to test on different devices
and screen sizes. It can also be quicker to update and test code changes on a virtual device.
However, it’s important to run your application on an actual device at some point in order
to get a better idea of how exactly it looks and feels.

By default, CRNA comes with live reload enabled. This means if you edit and save any file, the
application on your mobile device will automatically reload. Moreover, any build errors and logs
will be displayed directly in the terminal.
Let’s see what the directory structure of our app looks like. Open up a new terminal window.
Navigate to this app:
cd weather

And then run ls -a to see all the contents of the directory:
ls -a

If you’re using PowerShell or another non-Unix shell, you can just run ls.

Although your output will look slightly different based on your operating system, you should see
all the files in your directory listed:

29 https://facebook.github.io/react-native/docs/getting-started.html

Getting Started with React Native

├──
├──
├──
├──
├──
├──
├──
├──
├──
├──
└──

15

node_modules/
.babelrc
.flowconfig
.gitignore
.watchmanconfig
App.js
app.json
App.test.js
package.json
README.md
yarn.lock

Let’s go through each of these files:
• node_modules/ contains all third party packages in our application. Any new dependencies and
development dependencies go here.
• .babelrc allows us to define presets and plugins for configuring Babel30 . As we mentioned
previously, Babel is a transpiler that compiles newer experimental JavaScript into older versions
so that it stays compatible with different platforms.
• .flowconfig allows us to configure Flow31 , a static type checker for JavaScript. Flow is part
of the React Native toolchain and this file is included automatically in any React Native
application. We won’t be using Flow in this chapter but we will explore prop validations briefly
using prop-types.
• .gitignore is where we specify which files should be ignored by Git. We can see that both the
node_modules/ and .expo/ directories are already included.
• .watchmanconfig defines configurations for Watchman.
• App.js is where our application code lives.
• app.json is a configuration file that allows us to add information about our Expo app. The list
of properties that can be included in this file is listed in the documentation32 .
• App.test.js is included as a sample test file and contains a single test. CRNA is packaged with
Jest33 as its testing platform. We’ll go into detail about unit testing React Native applications
in the “Testing” chapter.
• package.json is where we provide information of the application to our package manager as
well as specify all our project dependencies.
• README.md is a markdown file commonly used to provide a description of a project.
• yarn.lock is where yarn keeps a record of the versions of each dependency installed.

package.json
Let’s take a closer look at the generated package.json file:
30 https://babeljs.io/
31 https://flow.org/
32 https://docs.expo.io/versions/v18.0.0/guides/configuration.html
33 https://facebook.github.io/jest/

Getting Started with React Native
1

{
"name": "weather",
"version": "0.1.0",
"private": true,
"devDependencies": {
"react-native-scripts": "1.14.0",
"jest-expo": "~27.0.0",
"react-test-renderer": "16.3.1"
},
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"scripts": {
"start": "react-native-scripts start",
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "jest"
},
"jest": {
"preset": "jest-expo"
},
"dependencies": {
"expo": "^27.0.1",
"react": "16.3.1",
"react-native": "~0.55.2"
}

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

16

}

The name and version properties are always required. The dependencies and devDependencies
define our application and development dependencies respectively.
Notice there are three devDependencies:
• react-native-scripts
• jest-expo
• react-test-renderer
The last two packages are related to testing. While CRNA is the tool that initializes our project,
the package react-native-scripts is the engine that runs our React Native app while in
development. When we specified the --scripts-version as 1.14.0 above, we were referring to
this package.
In our package.json, the scripts object defines all our script commands. These commands are all
handled by the react-native-scripts package. The commands yarn run start, yarn run android,
and yarn run ios allow us to start our application development server and/or run on a virtual device
or simulator. The scripts object also contains two other commands:

Getting Started with React Native

17

• yarn test runs all the Jest tests in our application
• yarn run eject starts the process of ejecting our application from the CRNA toolchain. As
we mentioned earlier, this can be necessary if we need to include a React Native library that
contains native code or if we need to write native code ourselves.

The utils/ directory
If you look at the book’s sample code, you’ll note that every application has a utils/ directory. This
directory contains helper functions that the application will use. You don’t need to concern yourself
with the details of these functions as they’re not relevant to the chapter’s core concepts.
When we reach the point in the application’s development where we need to use a utility provided
by utils/, we’ll remind you to copy over that folder from the sample code. You can also do this
immediately after initializing each project.

Components
With newer versions of JavaScript, we can define objects with properties using classes. React
Native lets us use this syntax to create components. Let’s take a look at a visual breakdown of the
components in our application:

18

Getting Started with React Native

Component Structure

We have an App component that represents the entire screen and contains the weather information
displayed to the use. Inside of this component, we have a SearchInput component that allows us to
search for different cities.

App
App is the first component created with a default CRNA application. Let’s take a look at its file:

Getting Started with React Native

19

weather/1/App.js
1
2

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

3
4
5
6
7
8
9
10
11
12
13
14

export default class App extends React.Component {
render() {
return (

Open up App.js to start working on your app!
Changes you make will automatically reload.
Shake your phone to open the developer menu.

);
}
}

15
16
17
18
19
20
21
22
23

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

Notice how we have a class defined in our file named App that extends React.Component. Using
extends allows us to declare a class as a subclass of another class. In here, we’ve defined App as
a subclass of React.Component. This is how we specify a specific class to be a component in our
application.
If you’d like to learn more about how classes work in JavaScript, refer to our Appendix.

We can also attach methods as properties to classes, and the same applies to component classes in
React Native. We can see we already have one for this component, the render method:

Getting Started with React Native

20

weather/1/App.js
5
6
7
8
9
10
11
12
13

render() {
return (

Open up App.js to start working on your app!
Changes you make will automatically reload.
Shake your phone to open the developer menu.

);
}

What we see on our device when launching our device matches what we see described in this
method. The render() method is the only required method for a React Native component. React
Native uses the return value from this method to determine what to render for the component.
When we use React Native, we represent different parts of our application as components. This
means we can build our app using different reusable pieces of logic with each piece displaying a
specific part of our UI. Let’s break down what we already have in terms of components:
• Our entire application is rendered with App as our top-level component. Although created
automatically as part of setting up a new CRNA project, this component is a custom component
responsible for rendering what we need in our application.
• The View component is used as a layout container.
• Within View, we use the Text component to display lines of text in our application. Unlike App,
both View and Text are built-in React Native components that are imported and used in our
custom component.
We can see that our App component uses and returns an HTML-like structure. This is JSX, which is
an extension of JavaScript that allows us to use an XML-like syntax to define our UI structure.

JSX
When we build an application with React Native, components ultimately render native views which
are displayed on our device. As such, the render() method of a component needs to describe how
the view should be represented. In other words, React Native allows us to describe a component’s
iOS and Android representation in JavaScript.
JSX was created to make the JavaScript representation of components easier to understand. It allows
us to structure components and show their hierarchy visually in markup. Consider this JSX snippet:

Getting Started with React Native

21



Hello, friend! I am a basic React Native component.



In here, we’ve nested a Text component within a View component. Notice how we use braces ({})
around an object ({ color: 'red' }) to set the style property value for Text. In JSX, braces are a
delimiter, signaling to JSX that what resides in-between the braces is a JavaScript expression. The
other delimiter is using quotes for strings, like this:


Even though the JSX above might look similar to HTML, it is actually just compiled into
JavaScript function calls (ex: React.createElement(View)). For this reason, we need to
import React at the top of any file that contains JSX. You can refer to the Appendix for
more detail.
During runtime React Native takes care of rendering the actual native UI for each component.

Props
We use the imported Text component to wrap each line of text output for our App component:
Open up App.js to start working on your app!

And we use the imported View component to wrap all the Text components:

...


Props allow us to pass parameters to components to customize their features. Here, View is used
to layout the entire content of the screen. We only have a single prop attached, style, that allows
us to pass in style parameters to adjust how our View component is rendered on our devices. Each
built-in component provided by React Native has its own set of valid props that we can use for
customization.

Getting Started with React Native

22

If you’re familiar with HTML, it’s very similar. For example, in HTML, say you wanted to insert an
image named image.png. You’d specify an img tag with a src attribute like this:


To give you an idea of the similarity, in React Native we can include images using the Image
component. We specify the location using the source prop:


We’ll cover images in greater detail later.

Like our View component, many components in React Native accept a style prop. Styling is a large
topic that we explore throughout this book. However, we can take a look at our styles object at the
bottom of App.js and get an idea of how it works:
weather/1/App.js
16
17
18
19
20
21
22
23

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

Web developers may recognize that this looks like CSS (Cascading Style Sheets) which is used to
style web pages. It’s important to note that styling in React Native does not use CSS. However, React
Native borrows a lot of styling nomenclature from web development. Here, we specify that the text
should be centered and that the background color should be white (#fff).
If you’ve used CSS before, you’ll find styling in React Native very familiar. If not, don’t
worry! It’s easy to get the hang of it.

Specifically, styles.container has the attributes flex, alignItems and justifyContent.
These are used to position the View in the center of the screen. React Native uses flexbox to
layout and align items consistently on different device sizes. We’ll go into more detail about
how exactly flexbox works in later chapters.

23

Getting Started with React Native

To build our weather app, we’ll start with layout and styling. Once we have some of the essence of
our weather app in place we can begin to explore strategies for managing data.
As we saw in the completed version of the app, we want our app to display the city, temperature,
and weather conditions as separate text fields. Although we’ll eventually interface with a weather
API in order to retrieve actual data, we’ll begin with hard-coding these values.

The completed app

Adding styles
To get a better handle on styling, let’s try adding an object with a color attribute to one of the text
fields:


Open up App.js to start working on your app!

Changes you make will automatically reload.
Shake your phone to open the developer menu.


Getting Started with React Native

Note that the outer-most set of brackets above are delimiters enclosing our JavaScript
statement. Inside of the delimiters is a JavaScript object. In React Native, if the object is
small enough it’s common to just write it all on one line.
However, the double brackets ({{}}) might be confusing. Here’s another way of writing the
same component:
const style = { color: 'red' };
return (


Open up App.js to start working on your app!


Save App.js. We can see our style applied once the application reloads:

24

Getting Started with React Native

25

As we mentioned previously, live reload is enabled by default in Expo. This means that with
any change to the code, the application will reload immediately. If you happen to not see any
changes reflected as soon as you save the file, you may have to check to see if this is enabled.
The documentation34 explains how to open up the developer menu and enable/disable the
feature.

Although we can style our entire component this way, a lot of inline styles (or style attributes defined
directly within the delimeter of the style prop) used in a component can make things harder to read
and digest.
We can solve this by leveraging React Native’s Stylesheet API to separate our styles from our
component. With Stylesheet, we can create styles with attributes similar to CSS stylesheets. We
can see that Stylesheet is already imported at the top of the file. It’s used to declare our first style,
styles.container, which we use for View. We can add a new style called red to our styles:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
red: {
color: 'red',
},
});

We’ll then have Text use this style:


Open up App.js to start working on your app!

Changes you make will automatically reload.
Shake your phone to open the developer menu.


If we save our file and take a look at our app, we can see that the end result is the same.
Now let’s add some appropriate styles and text fields in order to display some weather data for a
location. To add multiple styles to a single component, we can pass in an array of styles:
34 https://docs.expo.io/versions/latest/guides/up-and-running.html#cant-see-your-changes

Getting Started with React Native

26

weather/2/App.js
14
15
16

San Francisco
Light Cloud
24°

It is important to mention that when passing an array, the styles at the end of the array take
precedence over earlier styles, in case of any repeated attributes. We can see that we’re referencing
three new styles; textStyle, smallText, and largeText. Let’s define these within our styles object:
weather/2/App.js
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
textStyle: {
textAlign: 'center',
fontFamily: Platform.OS === 'ios' ? 'AvenirNext-Regular' : 'Roboto',
},
largeText: {
fontSize: 44,
},
smallText: {
fontSize: 18,
},

• textStyle specifies an alignment (center) as well as the fontFamily. Notice how we use
Platform to define platform specific fonts for both iOS and Android. We do this because both
operating systems provide a different set of native fonts.
• smallText and largeText both specify different font sizes.
Platform is a built-in React Native API. We’ll need to make sure to import it:

27

Getting Started with React Native

weather/2/App.js
1
2
3
4
5
6
7
8

import React from 'react';
import {
StyleSheet,
Text,
KeyboardAvoidingView,
Platform,
TextInput,
} from 'react-native';

Let’s take a look at our application now:

Styled Text

Platform specific properties
The Platform API allows us to conditionally apply different styles or properties in our component
based on the device’s operating system. The OS attribute of the object returns either iOS or android
depending on the user’s device.

Getting Started with React Native

28

Although this is a relatively simple way to apply different properties in our application based on
the user’s device, there may be scenarios where we may want our component to be substantially
different between operating systems.
We can also use the Platform.select method that takes the operating system as keys within an
object and returns the correct result based on the device:
1
2
3
4
5
6
7
8
9
10
11

textStyle: {
textAlign: 'center',
...Platform.select({
ios: {
fontFamily: 'AvenirNext-Regular',
},
android: {
fontFamily: 'Roboto',
},
}),
},

Separate files
Instead of applying conditional checks using Platform.OS a number times throughout the entire
component file, we can also leverage the use of platform specific files instead. We can create
two separate files to represent the same component each with a different extension: .ios.js and
.android.js. If both files export the same component class name, the React Native packager knows
to choose the right file based on the path extension. We’ll dive deeper into platform specific
differences later in this book.

Text input
We now have text fields that display the location, weather condition, and temperature. The next
thing we need to do is provide some sort of input to allow the user to search for a specific city.
Again, we’ll continue using hardcoded data for now. We’ll only begin using an API for real data
once we have all of our components in place.
React Native provides a built-in TextInput component that we can import into our component
that allows us to accept user input. Let’s include it within our View container underneath the Text
components (make sure to import it as well!):

Getting Started with React Native

29

weather/2/App.js

San Francisco
Light Cloud
24°


There are a number of props associated with TextInput that we can use. We’ll cover the basics
here but go into more detail about them in the “Core Components” chapter. Here we’re specifying
a placeholder, its color, as well as a style for the component itself. Let’s create its style object,
textInput, underneath our other styles:
weather/2/App.js

smallText: {
fontSize: 18,
},
textInput: {
backgroundColor: '#666',
color: 'white',
height: 40,
width: 300,
marginTop: 20,
marginHorizontal: 20,
paddingHorizontal: 10,
alignSelf: 'center',
},

As we mentioned previously, all the attributes that we provide styles with in React Native are
extremely similar to how we would apply them using CSS. Now let’s take a look at our application:

30

Getting Started with React Native

Text Input

We can see that the text input has a default underline on Android. We’ll go over how to remove this
in a bit.
We’ve also specified the clearButtonMode prop to be always. This shows a button on the right side
of the input field when characters are inserted that allows us to clear the text. This is only available
on iOS.

Text Input Clear Button

We can now type into the input field!

31

Getting Started with React Native

If you’re using the iOS simulator, you can connect your hardware keyboard and use that with
any input field. This can be done with Shift + ⌘ + K or going to Hardware -> Keyboard ->
Connect Hardware Keyboard

With this enabled, the software keyboard may not show by default. You can toggle this by
pressing ⌘ + K or going to Hardware -> Keyboard -> Toggle Software Keyboard
Now every time you click an input field, the software keyboard will display exactly how it
would if you were using a real device and you can type using your hardware keyboard.

However one thing you may have noticed is that when you focus on the input field with a tap, the
keyboard pops up and covers it on Android and comes quite close on iOS:

Keyboard

Since the virtual keyboard can cover roughly half the device screen, this is a common problem that occurs when using text inputs in an application. Fortunately, React Native includes
KeyboardAvoidingView, a component that solves this problem by allowing us to adjust where other
components render in relation to the virtual keyboard. Let’s import and use this component instead
of View:

Getting Started with React Native

32

weather/2/App.js

render() {
return (

San Francisco
Light Cloud
24°


);
}

Notice that KeyboardAvoidingView accepts a behavior prop with which we can customize how the
keyboard adjusts. It can change its height, position or bottom padding in relation to the position of
the virtual keyboard. Here, we’ve specified padding.
Now tapping the text input will shift our component text and input fields out of the way of the
software keyboard.

33

Getting Started with React Native

Keyboard Avoiding View

Custom components
So far, we’ve explored how to add styling into our application, and we’ve included some built-in
components into our main App component. We use View as our component container and import
Text and TextInput components in order to display hardcoded weather data as well as an input
field for the user to change locations.
It’s important to re-iterate that React Native is component-driven. We’re already representing our
application in terms of components that describe different parts of our UI without too much effort,
and this is because React Native provides a number of different built-in components that you can
use immediately to shape and structure your application.
However, as our application begins to grow, it’s important to begin thinking of how it can further
be broken down into smaller and simpler chunks. We can do this by creating custom components
that contain a small subset of our UI that we feel fits better into a separate, distinct component
file. This is useful in order to allow us to further split parts of our application into something more
manageable, reusable and testable.
Although our application in its current state isn’t extremely large or unmanageable, there’s still some
room for improvement. The first way we can refactor our component is to move our TextInput into

Getting Started with React Native

34

a separate component to hide its implementation details from the main App component. Let’s create
a components directory in the root of the application with the following file:
├── components/
- SearchInput.js

All the custom components we create that we use in our main App component will live inside this
directory. For more advanced apps, we might create directories within components to categorize
them more specifically. Since this app is pretty simple, let’s use a flat components directory.
The SearchInput will be our first custom component so let’s move all of our code for TextInput
from App.js to SearchInput.js:
weather/3/components/SearchInput.js
1
2

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

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

export default class SearchInput extends React.Component {
render() {
return (



);
}
}

20
21
22
23
24
25
26
27
28
29

const styles = StyleSheet.create({
container: {
height: 40,
marginTop: 20,
backgroundColor: '#666',
marginHorizontal: 40,
paddingHorizontal: 10,
borderRadius: 5,
},

Getting Started with React Native
30
31
32
33
34

35

textInput: {
flex: 1,
color: 'white',
},
});

Let’s break down what this file contains:
• We export a component named SearchInput.
• This component accepts a placeholder prop.
• This component returns a React Native TextInput with a few of its properties specified
wrapped within a View. We’ve applied the appropriate styles to our view container including
a borderRadius. We also added underlineColorAndroid="transparent" to remove the dark
underline that shows by default on Android.
this is a special keyword in JavaScript. The details about this are a bit nuanced, but for the
purposes of the majority of this book, this will be bound to the React Native component
class. So, when we write this.props inside the component, we’re accessing the props

property on the component. When we diverge from this rule in later sections, we’ll point it
out.
For more details on this, check out this page on MDN35 .

Custom props
As you may recall, in App.js we set the placeholder prop for TextInput to “Search any city.” That
renders the text input with a placeholder:

For SearchInput, we could hardcode a string again for placeholder. But what if we wanted to add
a search input elsewhere in our application? It would be nice if placeholder was customizable.
Earlier in this chapter, we explored how we can use props with a number of built-in components in
order to customize their features. We can also create props for custom components that we build as
well.
That’s what we do here in SearchInput. The component accepts the prop placeholder. In turn,
SearchInput uses this value to set the placeholder prop on TextInput.
35 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

Getting Started with React Native

36

The way data flows from parent to child in React Native is through props. When a parent renders
a child, it can send along props the child depends on. A component can access all its props through
the object this.props. If we decide to pass down the string "Type Here" as the placeholder prop,
the this.props object will look like this:
{ "placeholder": "Type Here" }

In here, we’ll set up App to render SearchInput which means that App is the parent of SearchInput.
Our parent component will be responsible for passing down the actual value of placeholder.
We’re getting somewhere interesting now. We’ve set up a custom SearchInput component and by
building it to accept a placeholder prop, we’re already setting it up to be configurable. Based on
what it receives, it can render any placeholder message that we’d like.

Importing components
In order to use SearchInput in App, we need to import the component first. We can remove the
TextInput logic from App.js and have App use SearchInput instead:
weather/3/App.js

import React from 'react';
import { StyleSheet, Text, KeyboardAvoidingView, Platform } from 'react-native';
import SearchInput from './components/SearchInput';
export default class App extends React.Component {
render() {
return (

San Francisco
Light Cloud
24°


);
}
}

By moving the entire TextInput details into a separate component called SearchInput, we’ve
made sure to not have any of its specific implementation details showing in the parent component
anymore. We can also remove the text input’s styling defined within the styles object.

Getting Started with React Native

37

There’s no specific answer to how often we should isolate different UI logic into separate custom
components. React Native was built in order to allow us to lay out our entire application in terms
of self-contained components, and that means we should separate parts of our application into
distinct units with custom functionality attached to them. This allows us to build a more manageable
application that’s easier to control and understand. We’ve isolated knowledge of our search input to
the component SearchInput and we’ll continue to isolate specific pieces of our app throughout this
chapter.
It’s common to separate your imports into two groups: imports from dependencies, and
imports from other files in your project. That’s why we put a blank line above SearchInput.
This comes down to personal style preference.

Background image
As we saw in the photo of the completed version of the app at the beginning of this chapter, we can
make our application more visually appealing by displaying a background image that represents the
current weather condition.
In this book’s sample code, we’ve included a number of images for various weather conditions. If you
inspect the weather/assets directory, you’ll find images like clear.png, hail.png, and showers.png.
If you’re following along, copy these two folders over from the sample code into your project:
1. weather/assets
2. weather/utils
We mentioned earlier that we’ve included a utils/ folder for each project in the book’s
sample code. This folder contains helper functions that we’ll use below.

If you’re on macOS or Linux, you can use cp -r to copy directories:
cp -r weather/{assets,utils} ~/react-native-projects/weather/

With the assets and utils folders copied over, let’s update our App component:

Getting Started with React Native

38

weather/4/App.js

import React from 'react';
import {
StyleSheet,
View,
ImageBackground,
Text,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import getImageForWeather from './utils/getImageForWeather';
import SearchInput from './components/SearchInput';
export default class App extends React.Component {
render() {
return (




San Francisco


Light Cloud

24°




);
}
}

In this component, we’re importing a getImageForWeather method from our utils directory which

Getting Started with React Native

39

returns a specific image from the assets directory depending on a weather type. For example,
getImageForWeather('Clear') returns the following image:

Feel free to peek into the implementation details of any function we use from the utils
directory to get a better idea of how it works.

We also import React Native’s built-in ImageBackground component. Let’s take a closer look at how
we’re making use of it in our render method:
weather/4/App.js

render() {
return (



Conceptually, the ImageBackground component is a View with an Image nested within. The source
prop accepts an image location, which we’ve set to getImageForWeather('Clear'). We know this

Getting Started with React Native

40

will always return the image displayed above. ImageBackground also uses the prop style for styling
the View container and the prop imageStyle for styling the image itself. Let’s add two new styles
and modify the container style:
weather/4/App.js

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#34495E',
},
imageContainer: {
flex: 1,
},
image: {
flex: 1,
width: null,
height: null,
resizeMode: 'cover',
},

Defining component styles with a flex attribute mean that they will expand to take up any room
remaining in their parent container in relation to any sibling components. They share this space in
proportion to their defined flex values. Since ImageBackground is the only nested element within
KeyboardAvoidingView, setting imageContainer to flex: 1 means that this element will fill up
the entire space of its parent component. We’ve removed justifyContent and alignItems from
container so that the ImageBackground can take up the entire device screen.
We also used flex: 1 to style the actual image itself, image, to make sure it takes up the entire
space of its parent container. With images in particular, the component will fetch and use the actual
width and height of the source image by default. For this reason, we’ve also set its height and width
attribues to null so that the dimensions of the image fit the container instead. The resizeMode
attribute allows us to define how the image is resized when the Image element does not match its
actual dimensions. Setting this attribute to cover means that the image will scale uniformly until it
is equal to the size of the component.
The “Core Components” chapter will dive deeper into how flexbox, layout, and the Image
component work in React Native

We also wrapped all of our Text elements and SearchInput within a view container styled with
detailsContainer:

Getting Started with React Native

41

weather/4/App.js



San Francisco


Light Cloud

24°



Now let’s set up its style:
weather/4/App.js

detailsContainer: {
flex: 1,
justifyContent: 'center',
backgroundColor: 'rgba(0,0,0,0.2)',
paddingHorizontal: 20,
},

Here, we’re ensuring the container within ImageBackground also fills up the entire space of its
parent component as well as have its items aligned at the center of the screen. We also add a semitransparent overlay to our image by setting the backgroundColor of this component.
The last thing we’ll need to do here is change our Text elements to white instead of black to show
more clearly with a background image:
weather/4/App.js

textStyle: {
textAlign: 'center',
fontFamily: Platform.OS === 'ios' ? 'AvenirNext-Regular' : 'Roboto',
color: 'white',
},

Try it out
Save the file and take a look at our app. We should now see the background image displayed!

Getting Started with React Native

42

Modifying location
The steps we’ve taken so far are quite common when starting React Native applications. We hardcode
all our data, organize our app into components, and get an idea of the visual layout as well as how
it breaks down into components.
However, our app really isn’t very useful at this moment. If we take a look at our SearchInput
component for instance, we can type anything into the input field but nothing actually happens as
a result. We need to find a way to track changes made to the component and store that information
somewhere. In other words, we need some piece of mutable data that updates whenever the user
changes or submits the input field.
Instead of having SearchInput not actually manage any data that represents the text inputted by
the user, let’s pass in a prop for it called location to reflect what the user has inputted into the text
input field:
render() {
const location = 'San Francisco';
return (



{location}

Light Cloud

24°





The reason we want to pass in the property that contains our location data is we need a way for our
child component to modify that field and communicate back up to our container App component.
Notice how we’ve moved the static string for location into a separate constant which we pass down
to SearchInput. We’ve instantiated it as San Francisco so that it can show as the first location when
the user loads the application. The next thing we just need to do is make sure that this location
constant is updated when the user actually changes the field in SearchInput:

Getting Started with React Native

43

export default class SearchInput extends React.Component {
handleChangeText(newLocation) {
// We need to do something with newLocation
}
render() {
return (

);
}
}

So what did we just do? We’ve just added onChangeText as a new prop to our TextInput component.
Notice that we don’t pass in a specific object or property, but a function instead:
onChangeText={this.handleChangeText}

This method is invoked everytime the text within the input field is changed. A number of builtin components provided by React Native include event-driven props which we can attach specific
methods to. We’ll explore more throughout this book.
With onChangeText, our TextInput returns the changed text as an argument which we’re attempting
to pass into a separate method called handleChangeText. Currently our method is blank and we’ll
explore how we can complete it in a bit.
In React Native, we need to pass in functions when we want to handle certain events related to the
component being referenced. For the TextInput component, onChangeText is set to fire every single
time the text within the input field has changed. We need to “listen” to this specific event in our child
component (TextInput) so that it can notify our parent component (SearchInput) to respond to this
event. To do this, we pass in a function that calls another function, or in other words, a callback.
This is a common pattern when building components which need to notify a parent component of
some event. Unfortunately with the way we’ve just set it up, it wouldn’t work in this example. This
is because the function handleChangeText has a different local scope than the component instance.
We can work around this by binding our function to the correct context of its this object.

Getting Started with React Native

44



Now this might seem okay for the current context, but it can quickly become unwieldy if we build
our components with bind statements in each event handler. One reason why is if we wanted to use
handleChangeText in multiple different sub-components for example, we would have to make sure
to bind it to the correct context every single time. To help solve this, we can take care of handling
our event using property initializers:
export default class SearchInput extends React.Component {
handleChangeText = (newLocation) => {
// We need to do something with newLocation
}
render() {
return (

);
}
}

This allows us to declare the member methods as arrow functions:
handleChangeText = (newLocation) => {
// We need to do something with newLocation
}

And we pass the method name to the prop and nothing more:

Getting Started with React Native

45

onChangeText={this.handleChangeText}

Property Initializers
Supported by Babel36 , property initializers are still in the proposal phase and have not yet
been slated for adoption in future JavaScript versions. Although this pattern is used quite
often in many React and React Native applications, it is important to keep in mind that it is
still experimental syntax.
For more information on the different ways to handle events in React Native, refer to the
Appendix.

Now that we’ve set up our callback correctly, let’s modify handleChangeText to change our text
prop in order to change the data to match what the user is typing:
handleChangeText = (newLocation) => {
this.props.location = newLocation;
};

Let’s run our application and try typing into the TextInput field. You’ll immediately notice that the
first location that shows is San Francisco, so we know that the text prop is being passed down
successfully!
However, if we type anything into our TextInput, you’ll notice nothing happens. Changing the text
within the input field does not actually update the parent location property and from the way
we’ve designed our component logic, it looks like it should. This is because this.props, which is
referenced in SearchInput, is actually owned by App and not the child component, SearchInput.
A component’s props are immutable and create a one-way data pipeline from parent to children.
We have a bit of a problem. We need to find a way to:
• store local data in our child component, SearchInput, that represents the value in the input
field
• track changes to the search input field as it’s updated by the user
• notify our parent component, App, whenever our location changes
This is where we can use a component’s state.

Storing local data
Let’s modify our SearchInput component once more. Currently the text input within the component
does nothing, so let’s add some local component state to control actual data. We can do this by adding
a constructor method to the component. We can then initialize the component’s state within this
method:
36 https://babeljs.io/docs/plugins/transform-class-properties/

Getting Started with React Native

46

weather/5/components/SearchInput.js

export default class SearchInput extends React.Component {
constructor(props) {
super(props);
this.state = {
text: '',
};
}

We can use the constructor method to to initialize our component-specific data, or state. We
do this here because this method fires before our component is mounted and rendered. Here, we
defined our state object to only contain a text property:
Remember, components in React Native are extended from React.Component to create
derived classes. super() is required in derived classes in order to reference this within
the constructor.

Much like how we can access the component’s props with this.props, we can access the component’s state via this.state. For example if we wanted to output our state property in a single Text
component, we could do this:
export default class HiThere extends React.Component {
constructor(props) {
super(props);
this.state = {
text: 'Hi there!',
};
}
render() {
return {this.state.text};
}
}

This component would now render 'Hi there!' since that’s how we defined our state.text
property in our constructor. For our current component however, our text property in state will
be used to define the text typed by the user into the input field. Let’s now modify our component’s
render method to allow for this:

Getting Started with React Native

47

weather/5/components/SearchInput.js

render() {
const { placeholder } = this.props;
const { text } = this.state;
return (



);
}

The first thing we did was destructure the component props and state objects:
weather/5/components/SearchInput.js

render() {
const { placeholder } = this.props;
const { text } = this.state;

Destructuring
Instead of using this.props.placeholder and this.state.text directy, we destructured
both objects at the beginning of our render method into individual variables (text and
placeholder). Please refer to the Appendix for more details on destructuring assignments.

We then make sure that the TextInput placholder prop is still accepting our props.placeholder
attribute. We also pass state.text to a value prop:

Getting Started with React Native

48

weather/5/components/SearchInput.js



The value prop is responsible for the content showed in the input field. With this, we now know
whatever is displayed in input field will always represent our local state.
We’ve also attached two additional props to our component, onChangeText and onSubmitEditing
with methods we haven’t set up yet.

Tracking changes to input
Let’s take a look at how onChangeText can allow us to update our state every time the input field is
changed. As we just did previously, we’re attaching a method to the onChangeText prop of TextInput:
weather/5/components/SearchInput.js

onChangeText={this.handleChangeText}

Previously, we set up a handleChangeText method that modifies our location prop value when the
user changes the text within the input. We quickly realized that this didn’t work. This is because
props are immutable and are always “owned” by a component’s parent while state can be mutated
and is “owned” by the component itself. This is an extremely important pattern to remember while
building components with React Native.
This brings us to setState(), a method we can use to change our state correctly. Let’s make use of
this in our handleChangeText method which we can declare right underneath our constructor:

Getting Started with React Native

49

weather/5/components/SearchInput.js

export default class SearchInput extends React.Component {
constructor(props) {
super(props);
this.state = {
text: '',
};
}
handleChangeText = text => {
this.setState({ text });
};

Shorthand property names
With later versions of JavaScript, we can define objects using shorthand form where possible.
Our handleChangeText method can also be written in a more explicit syntax:
handleChangeText = (text) => {
this.setState({ text: text });
};

Please refer to the Appendix for a little more detail on this concept.

Now we might be tempted to update our state by using this.state.text = text, but this will
not work. For all state modifications after the initial state we’ve defined in our constructor,
React provides components with the method setState() to do this. In addition to mutating the
component’s state object, this method triggers the React component to re-render, which is essential
after the state changes.
It’s good practice to initialize components with “empty” state as we’ve done in this component.
However, after our SearchInput component is initialized, we want to update the state for with data
the user types into the text input. This is why we use the text argument provided into our callback
method as part of the onChangeText prop and pass that into this.setState().
Never modify state outside of this.setState(). This function has important hooks around
state modification that we would be bypassing.
We discuss state management in detail throughout the book.

Getting Started with React Native

50

Notifying the parent component
So we’ve found a way to correctly store local state in our component that represents the text within
the search input and make sure that it updates as the user changes the value. We still need to do
one more thing which is to notify our parent App component when the user submits a new searched
value. This is why we’ve attached a method to the onSubmitEditing prop of TextInput:
weather/5/components/SearchInput.js

onSubmitEditing={this.handleSubmitEditing}

The idea here is we don’t necessarily want to communicate with our parent component everytime
the user changes the input field. That’s why onChangeText is purely responsible for storing the latest
typed input value into the local state of the component. Fortunately, the TextInput component has
an onSubmitEditing prop which fires when the user submits the field and not just changes it. This
happens specifically when the user presses the action button of the virtual keyboard in order to
submit their input. This is where we would want to notify our container component of the typed
user data. Let’s take a look at how we can set up the handleSubmitEditing function that we’re
passing in:
weather/5/components/SearchInput.js

handleSubmitEditing = () => {
const { onSubmit } = this.props;
const { text } = this.state;
if (!text) return;
onSubmit(text);
this.setState({ text: '' });
};

In here, we check if this.state.text is not blank (which means the user has typed something into
the field), and if that’s the case:
1. Run an onSubmit function obtained from the component’s props. We pass text as an
argument here.
2. Clear the text property in state using this.setState()
We’ve seen how this.props can be used to pass information down from a parent component to child
and we’ve also seen how built-in components such as TextInput can notify their parent component
through callbacks in some of their props. Similarly, we can create props in our custom components
to do the exact same thing. In here, we need SearchInput to communicate with the App component

Getting Started with React Native

51

whenever the user submits the input field. We do this because we want our parent component to
handle the event of the user typing and submitting a new city. This is why we have an onSubmit
prop here that gets fired.
The next thing we need to do is pass a method to the onSubmit prop of SearchInput in App and
handle the event:
weather/5/App.js



Let’s define local state for this component as well as the handleUpdateLocation method:
weather/5/App.js

export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
location: 'San Francisco',
};
}
handleUpdateLocation = city => {
this.setState({
location: city,
});
};

We defined local state for this component with just a location property and have it set to San
Francisco. We do this to ensure that an initial location is shown when we reload our application.
We also included a handleUpdateLocation method that takes in a parameter to change our location
state. This method will fire everytime the user submits the search input field because we pass this
method as the onSubmit prop for SearchInput.
Since we actually have “living” location data represented by what the user submits in the input field,
we can now display it in our first Text element instead of a hardcoded string:

52

Getting Started with React Native

weather/5/App.js

render() {
const { location } = this.state;
return (



{location}

Try it out
If we type any city in the search input and press return, we’ll see the name of the city being displayed
immediately.

Component State

Getting Started with React Native

53

This shows that we’ve wired everything correctly!
We’ve sequenced each of our problems step by step and showed how props and state differ when
trying to pass and store component data. However, handleUpdateLocation doesn’t really get real
weather information and just updates the city name that’s being displayed. We’ll wire it up to get
actual weather data soon.

Architecting state
We may have already considered controlling all the location state within SearchInput and not
having to deal with passing information upwards to a container component. There’s no specific
answer to where each piece of state should live and it depends on the type of application we’re
building. This is a core concept of building React Native applications and tools like Redux37 and
MobX38 aim to simplify this even further by allowing you to manage the entire state of the
application in a single location. However, even when we decide to use state management libraries
such as these examples, we still need to spend time deciding on how we want to structure our state
logic.
In our current app, we need to have App know the location data in order to display correct weather
conditions. SearchInput doesn’t really need to store this information without actually passing it up
to the component that handles the logic. The motivation behind keeping SearchInput simple is that
we can leverage React’s component-driven paradigm. We can re-use it in various places across our
application whenever we need a search input.
We can think of SearchInput as a component that provides presentational markup and does not
manage any real application data. Such components accept props from parent components which
specify the data a presentational component should render. This parent container component also
specifies behavior. If the lower level presentational component has any interactivity — like our
search input — it calls a prop-function given to it by the parent. We’ll go into more detail about
this important pattern throughout this book.

Lifecycle methods
We’ve wired up how our components communicate with each other to have a new location displayed
immediately when the user submits the text input field. However, you’ll notice that the city shows a
blank string when the app first loads. We could instantiate it with the name of an actual city instead
but we know we want to be getting actual weather information eventually. Although we haven’t set
that up just yet, the asynchronous action to fetch actual weather data for a city will be happening in
the handleUpdateLocation. Therefore it makes sense to call this method when our component first
loads. One thing we might be tempted to try is firing this method in our constructor:

37 https://github.com/reactjs/redux
38 https://github.com/mobxjs/mobx

Getting Started with React Native

54

constructor(props) {
super(props);
this.state = {
location: '',
};
this.handleUpdateLocation('San Francisco');
}

However, firing off asynchronous requests in the constructor is typically an anti-pattern. This is
because the constructor is called before the component is first mounted. As such, this method
should usually only be used to initialize state and bind methods.
Instead, we can make use of one of React Native’s lifecycle methods. Like the name suggests, these
methods allow you to access specific points in the lifecycle of a component. The term lifecycle here
applies to how React Native instantiates, changes and destroys components. We can use lifecycle
hooks to do something when these functions are called during different phases of component
rendering.
The most common lifecycle method used is the one that allows us to set component data after the
component is mounted – componentDidMount(). This method is commonly used to trigger network
requests to fetch data that the component would need. To understand when this method fires, let’s
add it to our component right after our constructor with a console.log:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
location: 'San Francisco',
};
}
componentDidMount() {
console.log('Component has mounted!');
}

When we reload our application, we can see Component has mounted! outputted directly to our
terminal as soon as the component has mounted.

Getting Started with React Native

55

Debugging in React Native
If you’ve worked with JavaScript on the web, you may be familiar with using console.log,
console.warn or console.error to output messages to the browser’s console for debugging
purposes. Similarly, Expo allows us to use these methods to output logs to our terminal. For
more detail about viewing logs, you can refer to the documentation39 .
Aside from logging, React Native also allows us to debug the JavaScript code in our app
using the Chrome Developer Tools. With Expo, we can do this by pressing Debug Remote JS
in the developer menu. You can refer to the documentation40 to learn more.

Now let’s update it to fire handleUpdateLocation:
weather/6/App.js

componentDidMount() {
this.handleUpdateLocation('San Francisco');
}

With this, we can remove San Francisco as our default location in state and set it to an empty
string.
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
location: '',
};
}

Since we’re using componentDidMount, we should still see San Francisco populated in place of the
text field as soon as we reload the app.
Although componentDidMount() allows us to create event listeners and fetch network
requests right after the component has rendered for the first time, there are number of other
lifecycle methods that React Native provides. We’ll go through each of them throughout this
book.
39 https://docs.expo.io/versions/latest/guides/logging.html
40 https://docs.expo.io/versions/latest/guides/debugging.html

Getting Started with React Native

56

Networking
We’ve built all the components that make up the UI of our app and refined it to show a nice
background image for the user. As we mentioned previously, the approach we’ve taken so far is
a common pattern used when building brand new React Native applications. We first organized our
views using components and then introduced some state and state management.
However, nobody will find our app useful unless it’s actually connected to real data. When building
a new mobile app, chances are we’ll need to communicate with a server. Communicating with a
server is a crucial component of most mobile applications.
For the purpose of this application, we’ll use the MetaWeather41 API to fetch real weather
information. MetaWeather is a weather data aggregator that calculates the most likely outcome
from predictions of different forecasters. They provide an API42 that provides this information over
a set of different endpoints:
1. Location search (/api/location/search/) which allows us to search for a particular city
2. Location weather information (/api/location/{woeid}) which provides a 5 day forecast for a
certain location
3. Location day which provides (/api/location/{woeid}/{date}/) forecast history and information for a particular day and location
WOEID, or Where On Earth ID43 , is a location identifier that allows us find details about a
specific location. For more detail on how exactly the MetaWeather API works, feel free to
take a closer look at the documentation44 .

Now that we have a basic understanding of how state and props control the flow of data between
different components, let’s move on to using this API to render real weather data. It’s possible to
put API calls directly in our component methods, but it’s usually a good idea to abstract that logic
away in its own file. In the utils directory, we’ve set up two separate API calls in api.js:
• fetchLocationId returns an array of locations based on a search query
• fetchWeather returns weather details about a specific location using a location identifier
known as Where On Earth ID45
The combination of both calls will allow us to search for a city and retrieve its weather information.
Feel free to open the file and take a look at how these methods work if you’re interested.
41 https://www.metaweather.com/
42 https://www.metaweather.com/api/
43 https://developer.yahoo.com/geo/geoplanet/guide/concepts.html
44 https://www.metaweather.com/api/
45 https://developer.yahoo.com/geo/geoplanet/guide/concepts.html

Getting Started with React Native

57

Async Functions
Callbacks and Promises are two ways to define asynchronous code in JavaScript. Built on
top of promises, async functions are a newer syntax that allows us to define asynchronous
methods in a synchronous manner. Both methods we’ve set up in api.js use this syntax.
Although supported by Babel, it is still in draft proposal stage and will most likely be ratified
into a future JavaScript release. Here’s the MDN46 resource if you happen to be interested
in learning more about this syntax further.

When building components that fetch information over the network, it’s inevitable that the user will
have to wait a certain period of time before the data is retreived. With most applications, it makes
sense to show a loading indicator of some sort so the user knows they have to wait a bit before they
can see the content. Fortunately, React Native provides a built-in ActivityIndicator component
that displays a circular loading spinner. Let’s update our root App component beginning with some
new imports:
weather/6/App.js

import React from 'react';
import {
StyleSheet,
View,
ImageBackground,
Text,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
StatusBar,
} from 'react-native';
import { fetchLocationId, fetchWeather } from './utils/api';
import getImageForWeather from './utils/getImageForWeather';
import SearchInput from './components/SearchInput';

We’ve added the following imports:
• ActivityIndicator is a built-in component that displays a circular loading spinner. We’ll use
it when data is being fetched from the network
• fetchLocationId, fetchWeather are the methods for interacting with the weather API
• StatusBar is a built-in component that allows us to modify the app status bar at the top of the
device
46 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

Getting Started with React Native

58

Now let’s make some changes to our component. We need to apply our network request logic and
store that information so that it can be easily displayed. We also need to make sure that a loading
indicator is shown while the request is firing. Let’s begin with updating our state:
weather/6/App.js

constructor(props) {
super(props);
this.state = {
loading: false,
error: false,
location: '',
temperature: 0,
weather: '',
};
}

We just expanded our state object to include loading, error, temperature, and weather in addition
to location. The three latter properties are data we’ll retreive from the API. The loading property
represents when a call is still being made (in order to show a loading icon) and error is used to store
the error message if our call fails or returns unusable information.
With setState, updates to our state can happen asynchronously. For this reason, the method accepts
a callback as an optional second parameter that allows us to define an action to fire after the state
is updated. Consider the following as an example:
export default class Example extends React.Component {
state = {
weather: '',
};
componentDidMount() {
this.setState({ weather: 'Clear' }, () => console.log(this.state));
}
}
// { weather: 'Clear' } is logged right after our component finishes mounting

We can apply this logic to the method responsible for interfacing with our external API: handleUpdateLocation:

Getting Started with React Native

59

weather/6/App.js

handleUpdateLocation = async city => {
if (!city) return;
this.setState({ loading: true }, async () => {
try {
const locationId = await fetchLocationId(city);
const { location, weather, temperature } = await fetchWeather(
locationId,
);
this.setState({
loading: false,
error: false,
location,
weather,
temperature,
});
} catch (e) {
this.setState({
loading: false,
error: true,
});
}
});
};

We’ve updated it to be an asynchronous function that uses setState to change our loading attribute
to true. We also pass in an asynchronous function as its second argument. In here, we first call
fetchLocationId with the user queried city (if present) and pass the location ID to fetchWeather to
return an object that contains the required information (location, weather, and temperature). Once
complete, our state is updated with the correct parameters. Moreover, if any of the calls happen to
error, the catch statement will update the error property in our state to true.
Now that we have our API logic in place, we’ll need to do a few things in the UI of our component:
• We need to display a loading spinner only when our API calls have fired but not completed
• We should show an error message if the user types in an incorrect address or our API call fails
• We need to render the correct weather information for a certain location
Let’s take a look at how we can update our render() method to do this:

Getting Started with React Native

weather/6/App.js
render() {

const { loading, error, location, weather, temperature } = this.state;
return (





{!loading && (

{error && (

Could not load weather, please try a different city.

)}
{!error && (


{location}


{weather}


{`${Math.round(temperature)}°`}


)}


)}

60

Getting Started with React Native

61




);
}

It might look like a lot is going on in the file, but let’s break it down piece by piece. We first included
our StatusBar component:
weather/6/App.js



The StatusBar component allows us to customize the status bar of our application using a barStyle
prop that lets us change the color of the text within the bar. A value of light-content renders a
lighter color (white) and dark-content will change it to a darker color (dark-grey).
With Expo, we can also configure the status bar for Android by modifying app.json.
Expo defaults barStyle for Android to light-content and makes the background translucent. Although this looks fine for our current application, you can remove the translucency
by providing a background color. Take a look at the documentation47 for more details.

We then added ActivityIndicator along with assigning its color and size prop:
weather/6/App.js



Notice how we’ve also included an animating prop which we’ve set to be our state.loading
attribute. This prop is responsible for showing or hiding the component entirely.
After that, we’ve included a curly brace container in our JSX:

47 https://docs.expo.io/versions/latest/guides/configuring-statusbar.html

Getting Started with React Native

62

weather/6/App.js

{!loading && (

{error && (

Could not load weather, please try a different city.

)}
{!error && (


{location}


{weather}


{`${Math.round(temperature)}°`}


)}


)}


We’ve previously seen how JSX allows us to embed JavaScript expressions within curly braces.
Fortunately, this lets us include operators as well, allowing us to conditionally render certain parts
of our UI. In here, !loading && <...> means that this statement will evaluate and display the
element if and only if loading is false. We can see we’ve pretty much wrapped most of the elements
that make up our component within here, and this makes sense since we don’t want to show any
text fields or the search input while the API call is being fetched.

Getting Started with React Native

63

Conditional Rendering
Using logical && operators within the render method is not the only way to conditionally
render parts of the component. At times, this approach can make it harder to read a
component file if a significant number of lines are being conditionally rendered.
If this happens, it might be a good idea to use helper methods. For example, our render
method can be rewritten following this pattern:
renderContent() {
const { error } = this.state;
return (

{error && Error}
{!error && this.renderInfo()}

);
}
renderInfo() {
const { info } = this.state;
return {info};
}
render() {
const { loading } = this.state;
return (


{!loading && this.renderContent()}

);
}

The React documentation48 goes into more detail about this concept as well as explaining
even more ways to conditionally render parts of components. Ultimately, it depends on
preference on which pattern to use.

Now within the content that shows when the API call isn’t being fired, we still need to be able to
display an appropriate error message if there’s an issue. We can use the state.error attribute to
conditionally display text in this scenario:
48 https://reactjs.org/docs/conditional-rendering.html

Getting Started with React Native

64

weather/6/App.js

{error && (

Could not load weather, please try a different city.

)}
{!error && (


{location}


{weather}


{`${Math.round(temperature)}°`}


)}


Notice how we now display our state information (location, weather, and temperature) in our
Text elements instead of hard-coded values. For temperature, we’re making use of the JavaScript
Math object and its round() method to round the temperature to the nearest integer.
The last thing we also do is pass the dynamic weather attribute to ImageBackground instead of a
hardcoded Clear string:
weather/6/App.js



Now if we run our application, typing a city into the input field will return its actual weather data!

Getting Started with React Native

65

We’ve pretty much finished connecting all the major points of our application by wiring in network
requests to retreive actual data. After slowly beginning with hardcoded data and building our
components that make up the building blocks of our UI, our application now works just as we
intended from the beginning of this chapter. The next few sections will explore some additional
enhancements to our code but won’t add any new functionality to our app.

PropTypes
With React Native, we can include validation functions using the prop-types library. This allows
us to specify and enforce the type of our component props and ensure that match what we expect
them to be. This can not only help us catch development errors sooner but also provide a layer of
documentation to the consumer of our components
We can add prop-types as a dependency:
yarn add prop-types

Now let’s take a look at how we can use PropTypes in SearchInput:

Getting Started with React Native

66

weather/6/components/SearchInput.js

SearchInput.propTypes = {
onSubmit: PropTypes.func.isRequired,
placeholder: PropTypes.string,
};
SearchInput.defaultProps = {
placeholder: '',
};

We’ve defined a propTypes object which instructs React to validate the props given to our
component. We’re specifying that onSubmit must be a function and placeholder must be a string.
We’ve also specified onSubmit to be required which means it has to be provided to out component
and is not optional.
We’ve left placeholder to be optional. For this, we’re making use of the defaultProps object. This
allows us to create our component and not specify placeholder if we don’t need to, defaultProps
will take care of providing it’s value in that case. It’s important to note that the value passed into
defaultProps also undergoes type-checking as well by the library.
Now what exactly happens when a prop’s type is not validated successfully? When a prop is passed
in with an invalid type or fails the propType validation, a warning is passed into the JavaScript
console. These warnings will only be shown in development mode, so if we accidentally deploy our
app into production with an improper use of a component, our users won’t see the warning.

Class properties
React Native includes class properties transformation49 from Babel that allows us to simplify how
we define our component state, props, and propTypes. For example, we can update the constructor
in App.js to:
weather/App.js

state = {
loading: false,
error: false,
location: '',
temperature: 0,
weather: '',
};

This gets transpiled into the exact same result as using a constructor. Similarly, we can simplify
how we define our state in SearchInput:
49 https://babeljs.io/docs/plugins/transform-class-properties/

Getting Started with React Native

67

weather/components/SearchInput.js

state = {
text: '',
};

Moreover, we can also set propTypes and defaultProps using static properties in our class. In other
words, we can remove the object references in SearchInput and define a static method within the
class:
weather/components/SearchInput.js

static propTypes = {
onSubmit: PropTypes.func.isRequired,
placeholder: PropTypes.string,
};
static defaultProps = {
placeholder: '',
};

Using this pattern and leveraging class properties transform is purely syntactical sugar over defining
methods and objects separately and allows us to write in a cleaner, simpler syntax.

Summary
Congratulations, we’ve just built our very first React Native application and covered almost all of
the essentials needed to build a complete and fully functional mobile app. We began by exploring
each of the files generated as a result of starting a new project with CRNA and how Expo allows us
to run our application smoothly on our device without worrying about Xcode and Android Studio
set up. We then built out each of the components that make up our application using the built-in
components provided by React Native. While doing so, we dove into the fundamentals of React
Native understanding JSX, how to apply custom styling as well as understanding how to use props
and state to manage and control data. We moved on to more complex topics including lifecycle
methods and how to use external network calls to provide real content to our application. Finally,
we finished off with a brief look into how propTypes can add an additional layer of safety by adding
type validation to our application. The rest of this book will dive deeper into core concepts of React
Native and the concepts learned in this chapter will serve as the foundation for everything else in
the text.
So far, we’ve only scratched the surface of what React Native allows us to do. By knowing the
setup/development details like Expo and the core concepts of props, state, and components, you
already have the essentials of React Native development under your belt. As of now, you can already
build a wide variety of applications using the framework – so go forth and build something amazing!

React Fundamentals
In the last chapter, we built our first React Native application. We explored how React applications
are organized by components. Using the key React concepts of state and props, we saw how data
is managed and how it flows between components. We also discussed other useful concepts, like
handling user input and fetching data from a remote API.
In this section, we’ll build another application step-by-step. We’ll dive even deeper into React’s
fundamentals. We’ll investigate a pattern that you can use to build React Native apps from scratch
and then put those steps to work to build a time-tracking application.
In this app, a user can add, delete, and modify various timers. Each timer corresponds to a different
task that the user would like to keep time for:

Time Tracking App

This app will have significantly more interactive capabilities than the one built in the last chapter.
As we’ll see, this will present us with some interesting challenges.

Getting started
This chapter assumes you’ve setup your system by following the steps at the beginning of the first
chapter.
As with all the chapters in this book, make sure you have the book’s sample code at the ready.

69

React Fundamentals

Previewing the app
Let’s begin by viewing the completed app. To try the completed app on your device:
• On Android, you can scan this QR code using the Expo app:

QR Code

• On iOS, you can navigate to the time-tracking/ directory within the sample code folder and
either preview it on the iOS simulator or send the link of the project URL to your device as we
explained in the previous chapter.
Play around with it to get a feel for all the functionality.

Breaking the app into components
Let’s start by breaking our app down into its components. As we noticed in our last project, visual
components usually map tightly to their respective React Native components. For example, we can
imagine that we’d want a Timer component for each timer:

React Fundamentals

70

Our application displays a list of timers and has a “+” icon at the top. We’re able to add new
timers to the list using this button. This “+” component is interesting because it has two distinct
representations. When the “+” button is pressed, the component changes into a form:

React Fundamentals

71

When the form is closed, the component changes back into a “+” button.
There are two approaches we could take. The first one is to have the parent component decide
whether or not to render a “+” component or a form component based on some piece of stateful
data. It could swap between the two children. However, this adds more responsibility to the parent
component. Since no other child components need this piece of information, it might make more
sense to have a new child component own the single responsibility of determining whether or not
to display a “+” button or a create timer form. We’ll call it ToggleableTimerForm. As a child, it can
either render the component TimerForm or the “+” button.
So, we’ve identified two components in addition to our root application component:

72

React Fundamentals

But the Timer component has a fair bit of functionality. As we saw in the completed version of the
app, each timer turns into a form when the user clicks “Edit”:

A single timer: Displaying time (left) vs. edit form (right)

In addition, timers delete themselves when “Remove” is pressed and have buttons for starting and
stopping. Do we need to break this up? And if so, how?
Displaying a timer and editing a timer are indeed two distinct UI components. They should be two
distinct React components. Like ToggleableTimerForm, we need some container component that
renders either the timer’s face or its edit form depending on if the timer is being edited.

React Fundamentals

73

We’ll call this EditableTimer. The child of EditableTimer will then be either a Timer component or
the edit form component. The form for creating and editing timers is very similar, so let’s assume
that we can use the component TimerForm in both contexts:

As for the other functionality of the timer, like the start and stop buttons, it’s a bit tough to determine
at this point whether or not they should be their own components. We can trust that the answers
will be more apparent after we’ve started writing some code and have a better idea of the general
structure of the components in our application.
So, we have our final component hierarchy, with some ambiguity around the final state of the timer
component:

React Fundamentals

74

• App: Root container
– EditableTimer: Displays either a timer or a timer’s edit form
* Timer: Displays a given timer
* TimerForm: Displays a given timer’s edit form
– ToggleableTimerForm: Displays a form to create a new timer
* TimerForm: Displays a new timer’s create form
For all the buttons in the app, we’ll create and use a component called TimerButton.

7 step process
Now that we have a good understanding of the composition of our components, we’re ready to build
a static version of our app that only contains hardcoded data. As we noticed in the previous chapter,
many applications we build will require our top-level component to communicate with a server. In
these scenarios, the server will be the initial source of state, and React Native will render itself
according to the data the server provides. If our current app followed this pattern it would also
send updates to the server, like when a timer is started. However, for simplicity, in this chapter we’ll
render local state rather than communicating with a server.

React Fundamentals

75

It always simplifies things to start off with static components, as we did in the last chapter. The
static version of the app will not be interactive. Pressing buttons, for example, won’t do anything.
But this will enable us to lay the framework for the app, getting a clear idea of how the component
tree is organized.
Next, we can determine what the state should be for the app and in which component it should live.
At that point, we’ll have the data flow from parent to child in place. Then we can add inverse data
flow, propagating events from child to parent.
In fact, this follows from a handy process for developing a React Native app from scratch:
1.
2.
3.
4.
5.
6.
7.

Break the app into components
Build a static version of the app
Determine what should be stateful
Determine in which component each piece of state should live
Hardcode initial states
Add inverse data flow
Add server communication (if present)

We followed this pattern in the last project:
1. Break the app into components
We looked at the desired UI and determined we wanted a custom SearchInput component.
2. Build a static version of the app
Our components started off without using state. Instead, we had our root App component pass down
location as a static prop to SearchInput.
3. Determine what should be stateful
In order for our application to become interactive, we had to be able to modify the search value of
the search input. The value submitted was our stateful location property.
4. Determine in which component each piece of state should live
Our root App component was responsible for managing the location, temperature, and weather
state parameters using React component class methods.
5. Hardcode initial state
We defined a hardcoded location value and passed it down to SearchInput as a custom prop.
6. Add inverse data flow
We defined the handleUpdateLocation function in our App container and passed it down in props
so that SearchInput could inform the parent of when our search input’s submit button is pressed.
7. Add server communication

React Fundamentals

76

We added server communication between our parent component and the MetaWeather API to
retrieve actual weather data.
These steps only serve as a guideline. You don’t necessarily have to follow it every time you build an
application, but you’ll likely internalize and become more accustomed to following this structure as
you build more applications. If steps in this process aren’t completely clear right now, don’t worry.
The purpose of this chapter is to familiarize yourself with this procedure.
We’ve already covered step (1) and have a good understanding of all of our components, except
for some uncertainty down at the Timer component. Step (2) is to build a static version of the app.
As in the last project, this amounts to defining React components, their hierarchy, and their HTML
representation. We avoid state for now.

Step 2: Build a static version of the app
Prepare the app
Before beginning, run the following commands in your terminal to create a new React Native app:
create-react-native-app time-tracking --scripts-version 1.14.0
cd time-tracking
yarn start

App
Let’s start off by writing our App component in the file App.js. We’ll begin with our imports:
time-tracking/1/App.js

import React from 'react';
import { StyleSheet, View, ScrollView, Text } from 'react-native';
import EditableTimer from './components/EditableTimer';
import ToggleableTimerForm from './components/ToggleableTimerForm';

After importing the core React Native components we’ll be using in App, we import EditableTimer
and ToggleableTimerForm. We’ll be implementing those shortly.
We’ll have our App component render both ToggleableTimerForm and a couple of EditableTimer
components. Because we’re building the static version of our app, we’ll manually set all the props:

React Fundamentals

77

time-tracking/1/App.js

export default class App extends React.Component {
render() {
return (


Timers







);
}
}

At the top, we display a title (“Timers”) inside of a Text component. We’ll look at the styles object
in a moment.
After our title, we render the rest of the components in a ScrollView component. The built-in
ScrollView component in React Native is responsible for wrapping components within a scrolling
container.
We’re passing down one prop to ToggleableTimerForm: isOpen. This is used by the child component
to determine whether to render a “+” or TimerForm. When ToggleableTimerForm is “open” the form
is being displayed.
We also include two separate EditableTimer components within App. We’ll dig into each of these
props when we build the component. Notably, isRunning specifies whether the timer is running and
editFormOpen specifies whether EditableTimer should display the timer’s face or its edit form.

React Fundamentals

78

Note that we don’t explicitly set any values for the props isRunning on the first EditableTimer or
editFormOpen on the second:
time-tracking/1/App.js




This is a style for boolean props you’ll often encounter in React Native apps. When no explicit value
is passed, the prop defaults to true. So  will give the same result
as . Conversely, when a prop is absent it is undefined. This
means that for the first timer editFormOpen is “falsy.”
ScrollView renders all of its components at once, even those not currently shown in the

screen.

Last, here are the styles we’re using:
time-tracking/1/App.js

const styles = StyleSheet.create({
appContainer: {
flex: 1,
},
titleContainer: {
paddingTop: 35,
paddingBottom: 15,
borderBottomWidth: 1,
borderBottomColor: '#D6D7DA',
},
title: {
fontSize: 18,

React Fundamentals

79

fontWeight: 'bold',
textAlign: 'center',
},
timerList: {
paddingBottom: 15,
},
});

We’re not going to focus on styles in this chapter so feel free to just copy over the styles object for
each component.

EditableTimer
With all of our child components, we’ll save their respective files within a components subdirectory.
Let’s create components/EditableTimer.js.
First, we’ll begin by implementing TimerForm and Timer. We’ll be creating those shortly:
time-tracking/1/components/EditableTimer.js

import React from 'react';
import TimerForm from './TimerForm';
import Timer from './Timer';

EditableTimer will either return a timer’s face (Timer) or a timer’s edit form (TimerForm) based on
the prop editFormOpen. We don’t anticipate this component will ever manage state.

So far, we’ve written React components as ES6 classes that extend React.Component. However,
there’s another way to declare React components: as functions.
Let’s see what that looks like:
time-tracking/1/components/EditableTimer.js

export default function EditableTimer({
id,
title,
project,
elapsed,
isRunning,
editFormOpen,
}) {
if (editFormOpen) {
return ;

React Fundamentals

80

}
return (

);
}
EditableTimer is a regular JavaScript function. In React, we call components written this way stateless functional components or functional components for short. While we can write EditableTimer

using either component style, it’s a perfect candidate to be written as a function.
Think of functional components as components that only need to implement the render() method.
They don’t manage state and don’t need any of React’s special lifecycle hooks.
Throughout this book, we’ll refer to the two different types as class components and
functional components.

Note that the props are passed in as the first argument to the function. We don’t use this when
working with functional components. Here, we use destructuring to extract all the props from the
props object.
The component’s render method switches on the prop editFormOpen. If true, we render a TimerForm.
Otherwise, we render Timer.
As we saw in App, this component receives six props. This component passes down the props id,
title and project to TimerForm. For Timer, we pass down all the timer attributes.

Benefits of functional components
Why would we want to use functional components? There are two main reasons:
First, using functional components where possible encourages developers to manage state in fewer
locations. This makes our programs easier to reason about.
Second, using functional components are a great way to create reusable components. Because
functional components need to have all their configuration passed from the outside, they are easy
to reuse across apps or projects.
A good rule of thumb is to use functional components as much as possible. If we don’t need any
lifecycle methods and can get away with only a render() function, using a functional component
is a great choice.

React Fundamentals

81

Note that React still allows us to set propTypes and defaultProps on functional components.

TimerForm
TimerForm will contain two TextInput fields for editing a timer’s title and project. We’ll also add
a pair of buttons at the bottom.

Like EditableTimer, we can write this component as a functional component:
time-tracking/1/components/TimerForm.js

import React from 'react';
import { StyleSheet, View, Text, TextInput } from 'react-native';
import TimerButton from './TimerButton';
export default function TimerForm({ id, title, project }) {
const submitText = id ? 'Update' : 'Create';
return (


Title





Project






React Fundamentals





);
}
const styles = StyleSheet.create({
formContainer: {
backgroundColor: 'white',
borderColor: '#D6D7DA',
borderWidth: 2,
borderRadius: 10,
padding: 15,
margin: 15,
marginBottom: 0,
},
attributeContainer: {
marginVertical: 8,
},
textInputContainer: {
borderColor: '#D6D7DA',
borderRadius: 2,
borderWidth: 1,
marginBottom: 5,
},
textInput: {
height: 30,
padding: 5,
fontSize: 12,
},
textInputTitle: {
fontSize: 14,
fontWeight: 'bold',
marginBottom: 5,
},
buttonGroup: {
flexDirection: 'row',
justifyContent: 'space-between',
},
});

82

React Fundamentals

83

We wrap each of our form elements in a View container. Each input field has a label (“Title” and
“Project”) above a TextInput.
At the end of the component, we have a button group with two TimerButton instances. We’ll create
this component in a bit.
Let’s take a closer look at how we’ve set up TextInput for the timer’s title:
time-tracking/1/components/TimerForm.js



And for the timer’s project:
time-tracking/1/components/TimerForm.js



Aside from adding styles using the style prop, we’re also using the TextInput component’s
defaultValue property. When the form is used for editing as it is here, we want the fields to be
populated with the current title and project values for this timer. Using defaultValue initializes
these fields with the current values, as desired.
Later, we’ll use TimerForm again within ToggleableTimerForm for creating timers.
ToggleableTimerForm will not pass TimerForm the title or project props. We’ll use
defaultProps to default these values to empty strings.

At the beginning of the component function, before the return statement, we define the variable
submitText. This variable uses the presence of props.id to determine what text the submit button
at the bottom of the form should display. If id is present, we know we’re editing an existing timer,
so it displays “Update.” Otherwise, it displays “Create.”
With all of this logic in place, TimerForm is prepared to render a form for creating a new timer or
editing an existing one.

React Fundamentals

84

We used an expression with the ternary operator to set the value of submitText. The syntax
is:
condition ? expression1 : expression2

If the condition is true, the ternary expression evaluates to expression1. Otherwise, it
evaluates to expression2. In our example, the variable submitText is set to the result of
the ternary expression.

TimerButton
Now let’s set up a component that we can use for all the buttons in our application, TimerButton.
Again, we can write this as a functional component:
time-tracking/1/components/TimerButton.js
1
2

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

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

export default function TimerButton({ color, title, small, onPress }) {
return (


{title}


);
}

22
23
24
25
26
27

const styles = StyleSheet.create({
button: {
marginTop: 10,
minWidth: 100,
borderWidth: 2,

React Fundamentals
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

85

borderRadius: 3,
},
small: {
fontSize: 14,
padding: 5,
},
large: {
fontSize: 16,
padding: 10,
},
buttonText: {
textAlign: 'center',
fontWeight: 'bold',
},
title: {
fontSize: 14,
fontWeight: 'bold',
},
elapsedTime: {
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
paddingVertical: 10,
},
});

React Native provides a built-in Button component, but it only allows for limited customization. For
this reason, we’re leveraging TouchableOpacity, which renders a wrapper to allow for components
to respond with opacity changes when pressed.
For easier customization, we’ve included color, title, and small as props that will allow us to
change how our button looks. The title prop is responsible for the button text while the color prop
changes the text and border colors. The small prop is a boolean prop passed in to render a smaller
button with slightly different styling.
Since we plan on using this component in multiple places in our app, we’ve defined an onPress prop
in order to fire a specific function that’s passed into our component when the button is pressed. We’re
not using it currently in TimerForm but we will as soon as we add actual data in our application.
TouchableOpacity accepts an activeOpacity prop that allows us to determine what the
opacity of the view should be when pressed. This defaults to a value of 0.2.

React Fundamentals

86

ToggleableTimerForm
Let’s turn our attention next to ToggleableTimerForm. Recall that this is a wrapper component
around TimerForm. It will display either a “+” or a TimerForm. Right now, it accepts a single prop,
isOpen, from its parent that instructs its behavior:
time-tracking/1/components/ToggleableTimerForm.js

import React from 'react';
import { StyleSheet, View } from 'react-native';
import TimerButton from './TimerButton';
import TimerForm from './TimerForm';
export default function ToggleableTimerForm({ isOpen }) {
return (

{isOpen ?  : }

);
}
const styles = StyleSheet.create({
container: {
paddingVertical: 10,
},
buttonPadding: {
paddingHorizontal: 15,
},
});

As noted earlier, TimerForm does not receive any props from ToggleableTimerForm. As such, its
title and project fields will be rendered empty.
We’re using a ternary operator again here to either return TimerForm or render a “+” button. You
could make a case that this should be its own component (say PlusButton) but at present we’ll keep
the code inside ToggleableTimerForm.

Timer
Time for the Timer component.
As with all projects in this book, the sample code for this project comes with a utils/ directory
that contains various functions that will aid in the construction of this app. We’ll be using one of

React Fundamentals

87

those functions now. If you haven’t already, go ahead and copy over time-tracking/utils/ from
the sample code to your project directory now.
With utils/ in place, let’s take a look at our first version of Timer:
time-tracking/1/components/Timer.js

import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { millisecondsToHuman } from '../utils/TimerUtils';
import TimerButton from './TimerButton';
export default function Timer({ title, project, elapsed }) {
const elapsedString = millisecondsToHuman(elapsed);
return (

{title}
{project}
{elapsedString}






);
}
const styles = StyleSheet.create({
timerContainer: {
backgroundColor: 'white',
borderColor: '#d6d7da',
borderWidth: 2,
borderRadius: 10,
padding: 15,
margin: 15,
marginBottom: 0,
},
title: {
fontSize: 14,
fontWeight: 'bold',
},
elapsedTime: {

React Fundamentals

88

fontSize: 26,
fontWeight: 'bold',
textAlign: 'center',
paddingVertical: 15,
},
buttonGroup: {
flexDirection: 'row',
justifyContent: 'space-between',
},
});

The elapsed prop in this app is in milliseconds. This is the representation of the data that React will
keep. This is a good representation for machines, but we want to show our users a more humanreadable format.
We use a function defined in ./utils/TimerUtils, millisecondsToHuman(). You can pop open that
file if you’re curious about how it’s implemented. The string it renders is in the format ‘HH:MM:SS’.
Note that we could store elapsed in seconds as opposed to milliseconds, but JavaScript’s
time functionality is all in milliseconds. We keep elapsed consistent with this for simplicity.

Try it out
With all of our components laid out, let’s boot up the React Native packager to see our app so far:

React Fundamentals

89

Tweak some of the props and refresh to see the results. For example:
• Flip the prop passed down to ToggleableTimerForm from false to true and see the timer form
render in the place of the “+” button:

React Fundamentals

90

• Remove the editFormOpen prop in the second EditableTimer component within App and
witness the component flip the child it renders accordingly:

React Fundamentals

91

To review, our App component currently renders a ToggleableTimerForm component and two
EditableTimer components.
ToggleableTimerForm renders either a “+” or a TimerForm based on the prop isOpen.
EditableTimer renders either Timer or TimerForm based on the prop editFormOpen. Timer and
TimerForm are our app’s bottom-level components. They hold the majority of the screen’s UI. The

components above them are primarily concerned with orchestration.
So far, we’ve used hardcoded props to pass data around our app. But in order to enhance our app
with interactivity, we must evolve it from its static existence to a mutable one. As we saw in the last
chapter, in React we use state to accomplish this.

Step 3: Determine what should be stateful
Before introducing state, we need to determine what, exactly, should be stateful. Let’s start by
collecting all of the data that’s consumed by each component in our static app. In our static app,
data will be wherever we are defining or using props. We will then determine which of that data
should be stateful.
App

React Fundamentals

92

This declares two child components. It sets one prop, which is the isOpen boolean that is passed
down to ToggleableTimerForm.
EditableTimer

This uses the prop editFormOpen and also accepts all the attributes of a timer.
Timer

This uses all the attributes for a timer.
TimerForm

This has two interactive input fields, one for title and one for project. When editing an existing
timer, these fields are initialized with the timer’s current values.

State criteria
We can apply criteria to determine if data should be stateful:
These questions are from the excellent article by Facebook called “Thinking In React.” You
can read the original article here50 .

1. Is it passed in from a parent via props? If so, it probably isn’t state.
A lot of the data used in our child components are already listed in their parents. This criterion helps
us de-duplicate.
For example, “timer properties” is listed multiple times. When we see the properties declared in
EditableTimer, we can consider it state. But when we see it elsewhere, it’s not.
2. Does it change over time? If not, it probably isn’t state.
This is a key criterion of stateful data: it changes.
3. Can you compute it based on any other state or props in your component? If so, it’s not
state.
For simplicity, we want to strive to represent state with as few data points as possible.

Applying the criteria
App

• isOpen boolean for ToggleableTimerForm and timer properties for EditableTimer
50 https://facebook.github.io/react/docs/thinking-in-react.html

React Fundamentals

93

Stateful. The data is defined here. It changes over time. And it cannot be computed from other state
or props.
• timer attributes
Stateful. We define the data on each EditableTimer here. This data is mutable. And it cannot be
computed from other state or props.
EditableTimer

• editFormOpen for a given timer
Stateful. The data is defined here. It changes over time. And it cannot be computed from other state
or props.
Timer

• Timer properties
In this context, not stateful. Properties are passed down from the parent.
TimerForm

We might be tempted to conclude that TimerForm doesn’t manage any stateful data, as title and
project are props passed down from the parent. However, as saw with our SearchInput component
in the previous chapter, components that use TextInput can be special state managers in their own
right – these components often maintain the value of the input field as state.
So, outside of TimerForm, we’ve identified our stateful data:
• The list of timers and properties of each timer
• Whether or not the edit form of a timer is open
• Whether or not the create form is open

Step 4: Determine in which component each piece of
state should live
While the data we’ve determined to be stateful might live in certain components in our static app,
this does not indicate the best position for it in our stateful app. Our next task is to determine the
optimal place for each of our three discrete pieces of state to live.
This can be challenging at times but, again, we can apply the following steps from Facebook’s guide
“Thinking in React51 ” to help us with the process:
51 https://facebook.github.io/react/docs/thinking-in-react.html

React Fundamentals

94

For each piece of state:
• Identify every component that renders something based on that state.
• Find a common owner component (a single component above all the components
that need the state in the hierarchy).
• Either the common owner or another component higher up in the hierarchy should
own the state.
• If you can’t find a component where it makes sense to own the state, create a new
component simply for holding the state and add it somewhere in the hierarchy above
the common owner component.
Let’s apply this method to our application:

The list of timers and attributes of each timer
At first glance, we may be tempted to conclude that App does not appear to use this state. Instead, the
first component that uses this state is EditableTimer. We might think it would be wise to move timer
attributes into the EditableTimer component’s state as opposed to passing them down as props.
While this may be the case for displaying timers, modifying them, and deleting them, what about
creating them? ToggleableTimerForm does not need the state to render, but it can affect state. It
needs to be able to insert a new timer. It will propagate the data for the new timer up to the root
App.
Therefore, App is truly the common owner. It renders EditableTimer components by passing down
timer state. It can handle modifications from EditableTimer and creates from ToggleableTimerForm,
mutating the state. The new state will then flow downward through EditableTimer via props.

Whether or not the edit form of a timer is open
In our static app, App specifies whether or not an EditableTimer should be rendered with its edit
form open. Technically, though, this state could just live in each individual EditableTimer. No parent
component in the hierarchy depends on this data.
Storing the state in EditableTimer will be fine for our current needs. But there are a few requirements
that might require us to “hoist” this state up higher in the component hierarchy in the future.
For instance, what if we wanted to impose a restriction such that only one form, including the create
form, could be open at a time? Then it would make sense for App to own the state, as it would need
to inspect it to determine whether to allow for another form to open.

Visibility of the create form
App doesn’t appear to care about whether ToggleableTimerForm is open or closed. It feels safe to
reason that the state can just live inside ToggleableTimerForm itself.

So, in summary, we’ll have three pieces of state each in three different components:

React Fundamentals

95

• Timer data will be owned and managed by App.
• Each EditableTimer will manage the state of its timer edit form.
• The ToggleableTimerForm will manage the state of its form visibility.

Step 5: Hardcode initial states
We’re now well prepared to make our app stateful. We’ll define our initial states within the
components themselves. This means hardcoding a list of timers in the top-level component, App.
For our two other pieces of state, we’ll have the components’ forms closed by default.
After we’ve added initial state to a parent component, we’ll make sure our props are properly
established in its children.

Adding state to App
Let’s start by modifying App to hold the timer data in state.
We’ll be using the npm library uuid52 to generate ids for each of our timers. The library’s function
uuidv4() will randomly generate a Universally Unique IDentifier53 for each of our timers.
A UUID is a string that looks like this:
2030efbd-a32f-4fcc-8637-7c410896b3e3

First, in your console, install the library:
yarn add uuid

Then, at the top of App.js, import the uuidv4() function:
time-tracking/2/App.js

import uuidv4 from 'uuid/v4';

Next, we’ll initialize the component’s state to an array of two timer objects. This will give us a list
of timers to play with when we open the app:

52 https://www.npmjs.com/package/uuid
53 https://en.wikipedia.org/wiki/Universally_unique_identifier

React Fundamentals

96

time-tracking/2/App.js

export default class App extends React.Component {
state = {
timers: [
{
title: 'Mow the lawn',
project: 'House Chores',
id: uuidv4(),
elapsed: 5456099,
isRunning: true,
},
{
title: 'Bake squash',
project: 'Kitchen Chores',
id: uuidv4(),
elapsed: 1273998,
isRunning: false,
},
],
};

We set the initial state to an object with the key timers. timers points to an array with two hardcoded
timer objects.
As in the previous chapter, we’re leaning on the Babel plugin transform-class-properties
to help simplify how we define our initial state.

Below, in render, we’ll use state.timers to generate an array of EditableTimer components. Each
will be derived from an individual object in the timers array that’s being passed in as a prop. We’ll
use map to do so:
time-tracking/2/App.js

render() {
const { timers } = this.state;
return (


Timers



React Fundamentals

97


{timers.map(({ title, project, id, elapsed, isRunning }) => (

))}


);
}

The rendered UI of the component ends up being an array of EditableTimer components:
[
,

]

Notably, we’re able to represent the EditableTimer component instance in JSX inside of return. It
might seem odd at first that we’re able to have a JavaScript array of JSX elements, but remember
that Babel will transpile the JSX representation of each EditableTimer () into
regular JavaScript.

React Fundamentals

98

If you’re interested in how this compiles, please refer to the Appendix.

Note the use of the key={timer.id} prop. The key prop is not used by our EditableTimer
component but by the React Native framework. It’s a special property that we discuss deeper
in the next chapter “Core Components.” For the time being, it’s enough to note that this
property needs to be unique per React Native component in a list.

Array’s map()
If you’re unfamiliar with the map method, it takes a function as an argument and calls it with
each item inside of the array and builds a new array by using the return value from each
function call.
Since the timers array has two items, map will call this function twice, once for each timer.
When map calls this function, it passes in as the first argument an item. The return value
from this function call is inserted into the new array that map is constructing. After handling
the last item, map returns this new array. Here, we’re rendering this new array within our
render() method.

Props vs. state
Let’s take a step back and reflect on the difference between props and state again. What existed as
mutable state in App is passed down as immutable props to EditableTimer.
We talked at length about what qualifies as state and where state should live. Mercifully, we do not
need to have an equally lengthy discussion about props. Once you understand state, you can see
how props act as its one-way data pipeline. State is managed in some select parent components
and then that data flows down through children as props.
If state is updated, the component managing that state re-renders by calling render(). This, in turn,
causes any of its children to re-render as well. And the children of those children. And on and on
down the chain.
Let’s continue our own march down the chain.

Adding state to EditableTimer
In the static version of our app, EditableTimer relied on editFormOpen as a prop to be passed down
from the parent. We decided that this state could actually live here in the component itself.
Because this component will actually manage state, we’ll need to change it from a functional
component to a class component.
We’ll set the initial value of editFormOpen to false, which means that the form starts off as closed:

React Fundamentals

99

time-tracking/2/components/EditableTimer.js

export default class EditableTimer extends React.Component {
state = {
editFormOpen: false,
};
render() {
const { id, title, project, elapsed, isRunning } = this.props;
const { editFormOpen } = this.state;
if (editFormOpen) {
return ;
}
return (

);
}
}

Timer remains stateless
If you look at Timer, you’ll see that it does not need to be modified to include state. It has been using
exclusively props and is so far unaffected by our current refactor.

Adding state to ToggleableTimerForm
We know that we’ll need to tweak ToggleableTimerForm as we’ve assigned it some stateful
responsibility. We want to have the component manage the state isOpen.
We’ll initialize the state to a “closed” state at the top of the component:

React Fundamentals

100

time-tracking/2/components/ToggleableTimerForm.js

export default class ToggleableTimerForm extends React.Component {
state = {
isOpen: false,
};

Next, we’ll define a function that toggles the state of the form to open:
time-tracking/2/components/ToggleableTimerForm.js

handleFormOpen = () => {
this.setState({ isOpen: true });
};

Finally, we’ll modify the component’s render() method to include our app’s first piece of interactivity. We’ll switch off of isOpen to determine whether we should render the “+” button or a TimerForm.
We’ll set handleFormOpen as the onPress handler for TimerButton:
time-tracking/2/components/ToggleableTimerForm.js

render() {
const { isOpen } = this.state;
return (

{isOpen ? (

) : (

)}

);
}

If you remember, we created TimerButton to accept an onPress prop which is passed down to
the onPress action of the TouchableOpacity within. TouchableOpacity is a built-in React Native
component. When it is pressed, it will invoke its onPress handler. TimerButton passes along its own
onPress prop directly to TouchableOpacity.
Therefore, when the TimerButton is pressed, the function handleFormOpen() will be invoked.
handleFormOpen() modifies the state, setting isOpen to true. This causes the ToggleableTimerForm
component to re-render. When render() is called this second time around, this.state.isOpen is
true and ToggleableTimerForm renders TimerForm. Neat.

React Fundamentals

101

As we explored in the last chapter, we are writing the handleFormOpen() function as a
property initializer (i.e. using an arrow function) in order to ensure this inside the function
is bound to the component. React will automatically bind class methods corresponding to
the component API (like render and componentDidMount) to the component for us.

Our updated ToggleableTimerForm, in full:
time-tracking/2/components/ToggleableTimerForm.js

export default class ToggleableTimerForm extends React.Component {
state = {
isOpen: false,
};
handleFormOpen = () => {
this.setState({ isOpen: true });
};
render() {
const { isOpen } = this.state;
return (

{isOpen ? (

) : (

)}

);
}
}

Adding state to TimerForm
We mentioned earlier that TimerForm would manage state as it includes a form. In React Native,
forms are stateful.
Recall that TimerForm includes two input fields:

React Fundamentals

102

These input fields are modifiable by the user. In React Native, all modifications that are made to a
component should be handled and kept in state. This includes changes like the modification of an
input field. The best way to understand this is to see what it looks like.
To make these input fields stateful, we can make our component stateful and initialize state at the
top of our component:
time-tracking/2/components/TimerForm.js

export default class TimerForm extends React.Component {
constructor(props) {
super(props);
const { id, title, project } = props;
this.state = {
title: id ? title : '',
project: id ? project : '',
};
}

Our state object has two properties, each corresponding to an input field that TimerForm manages.
If TimerForm is creating a new timer as opposed to editing an existing one, the id prop will be
undefined. In that case, we initialize both properties to a blank string ('') using ternary operators.
Otherwise, when this form is editing a timer, we’ll want to set both values to their respective prop
values.
Note that because we’re checking and defining our state based on props, we’re using the constructor()
for state initialization instead of defining state as a class property.
We want to avoid initializing title or project to undefined. That’s because the value of
an input field can’t technically ever be undefined. If it’s empty, its value in JavaScript is a
blank string.

In our first pass at building this component, we used defaultValue to set the initial state of the
TextInput fields based on props. But the defaultValue prop only sets the value of the TextInput

React Fundamentals

103

for the initial render. Instead of using defaultValue, we can connect our input fields directly to our
component’s state using value. We could do something like this:


With this, our input fields would be driven by state. Whenever either of our state properties change,
our input fields would be updated to reflect the new value.
However, this misses a key ingredient: We don’t currently have any way for the user to modify
this state. The input field will start off in-sync with the component’s state. But the moment the user
makes a modification, the input field will become out-of-sync with the component’s state.
We can fix this by using React Native’s onChangeText prop for TextInput components. Like onPress
for a button component, we can set onChangeText to a function like we did in our previous chapter.
Whenever the input field is changed, React will invoke the function specified.
Let’s set the onChangeText attributes on both input fields to functions we’ll define next. For our title
input field:
time-tracking/2/components/TimerForm.js



And similarly for project:
time-tracking/2/components/TimerForm.js



The functions handleTitleChange and handleProjectChange will both modify their respective
properties in state. Here’s what they look like:

React Fundamentals

104

time-tracking/2/components/TimerForm.js

handleTitleChange = title => {
this.setState({ title });
};
handleProjectChange = project => {
this.setState({ project });
};

When React Native invokes the function passed to onChangeText, it invokes the function with the
changed text passed as the argument. With this, we update the state to the new value of the input
field.
Using a combination of state, the value attribute, and the onChangeText attribute is the
canonical method we use to write form elements in React Native.
Our updated TimerForm component, in full:
time-tracking/2/components/TimerForm.js

export default class TimerForm extends React.Component {
constructor(props) {
super(props);
const { id, title, project } = props;
this.state = {
title: id ? title : '',
project: id ? project : '',
};
}
handleTitleChange = title => {
this.setState({ title });
};
handleProjectChange = project => {
this.setState({ project });
};
render() {
const { id } = this.props;
const { title, project } = this.state;

React Fundamentals

105

const submitText = id ? 'Update' : 'Create';
return (


Title





Project









);
}
}

To recap, here’s an example of the lifecycle of TimerForm:
1.
2.
3.
4.
5.

On the page is a timer with the title “Mow the lawn.”
The user toggles open the edit form for this timer, mounting TimerForm to the screen.
TimerForm initializes the state property title to the string "Mow the lawn".
The user modifies the title input field, changing it to the value "Cut the grass".
With every keystroke, React invokes handleTitleChange. The internal state of title is kept
in-sync with what the user sees on the page.

React Fundamentals

106

With TimerForm refactored, we’ve finished establishing our stateful data inside our components.
And we’ve assembled our downward data pipeline, props.
We’re ready — and perhaps a bit eager — to build out interactivity using inverse data flow. But
before we do, let’s save and reload the app to ensure everything is working.
Try it out
We expect to see new example timers based on the hardcoded data in App. We also expect pressing
the “+” button toggles open a form:

Step 6: Add inverse data flow
As we saw in the last chapter, children communicate with parents by calling functions that are
provided to them via props. In our weather app, when our search field was submitted with a value,
SearchInput didn’t do any data management. Instead, it called a function given to it by App, which
was then able to manage state accordingly.
We are going to need inverse data flow in two areas:
• TimerForm needs to propagate create and update events (create while under ToggleableTimerForm
and update while under EditableTimer). Both events will eventually reach our top level App.
• Timer has a fair amount of behavior. It needs to handle delete and edit press, as well as the
start and stop timer logic.
Let’s start with TimerForm.

React Fundamentals

107

TimerForm
To get a clear idea of what exactly TimerForm will require, we’ll start by adding event handlers to it
and then we’ll work our way backwards up the hierarchy.
TimerForm needs two event handlers:

• When the form is submitted (creating or updating a timer)
• When the “Cancel” button is pressed (closing the form)
TimerForm will receive two functions as props to handle each event. The parent component that uses
TimerForm is responsible for providing these functions:

• props.onFormSubmit(): called when the form is submitted
• props.onFormClose(): called when the “Cancel” button is pressed
As we’ll see soon, this enables the parent component to determine what the behavior should be
when these events occur.
Let’s first add onFormClose to the props being destructured in the component’s render method:
time-tracking/3/components/TimerForm.js

render() {
const { id, onFormClose } = this.props;

We’ll then modify the buttons on TimerForm by specifying onPress props for each:
time-tracking/3/components/TimerForm.js






React Fundamentals

108

The onPress prop for the “Submit” button specifies the function this.handleSubmit, which we’ll
define next. The onPress prop for the “Cancel” button specifies the prop onFormClose directly.
Now that we’ve seen how we’ll use handleSubmit, let’s write it. Declare this function above
render():
time-tracking/3/components/TimerForm.js

handleSubmit = () => {
const { onFormSubmit, id } = this.props;
const { title, project } = this.state;
onFormSubmit({
id,
title,
project,
});
};

Again, we’re working bottom-up right now. So the handleSubmit() method calls the anticipated
function onFormSubmit() which we’ll write in a moment. It passes in a data object with id, title,
and project attributes.
Notice that we’re reading id via props and reading title and project from state. This is because we
want to supply the function with the up-to-date values of title and project (in state) as opposed
to the initial values (supplied as props).

ToggleableTimerForm
Let’s follow the submit event from TimerForm as it bubbles up the component hierarchy. First, we’ll
modify ToggleableTimerForm. We need it to define and pass down two prop-functions to TimerForm:
onFormClose() and onFormSubmit().
Let’s update the component’s render() method first:
time-tracking/4/components/ToggleableTimerForm.js

render() {
const { isOpen } = this.state;
return (

{isOpen ? (

) : (

)}

);
}

We pass in two functions as props to TimerForm. As we’ve seen, functions are just like any other
prop.
Let’s write handleFormClose() first:
time-tracking/4/components/ToggleableTimerForm.js

handleFormClose = () => {
this.setState({ isOpen: false });
};

Now, what might handleFormSubmit() look like? ToggleableTimerForm is not the manager of timer
state. So ToggleableTimerForm should expect a new prop, onFormSubmit() from App. ToggleableTimerForm
should, in turn, pass this down to TimerForm. When the user submits a form down in TimerForm,
they’ll be invoking a function defined up in App that modifies the timer state. ToggleableTimerForm
is just a proxy of this function.
So, we might be tempted to just pass this anticipated prop-function directly to TimerForm like this:


However, consider this: after the user clicks “Create” to create a timer, we actually want to close
ToggleableTimerForm. We’ll want to intercept this event so that we can set isOpen to false.
To do this, here’s what handleFormSubmit() looks like:

React Fundamentals

110

time-tracking/4/components/ToggleableTimerForm.js

handleFormSubmit = timer => {
const { onFormSubmit } = this.props;
onFormSubmit(timer);
this.setState({ isOpen: false });
};

The handleFormSubmit() method accepts the argument timer and passes it along to onFormSubmit().
Recall that in TimerForm this argument is an object containing the desired timer properties. After
invoking onFormSubmit(), handleFormSubmit() calls setState() to close its form.
Although we’re not adding server communication in this chapter, let’s try and visualize how
submitting the form would work if we did.
The result of onFormSubmit() will not impact whether or not the form is closed. We invoke
onFormSubmit(), which may eventually create an asynchronous call to a server. Execution
will continue before we hear back from the server which means setState() will be called.
If onFormSubmit() fails — such as if the server is temporarily unreachable — we’d ideally
have some way to display an error message and re-open the form.

App
We’ve reached the top of the hierarchy, our root App component. As this component will be
responsible for the data for the timers, it is here that we will define the logic for handling the events
we’re capturing down at the lowest-level components.
The first event we’re concerned with is the submission of a form (i.e. events from TimerForm). When
this happens, either a new timer is being created or an existing one is being updated. We’ll create
two separate functions to handle these two distinct events:
• handleCreateFormSubmit() will handle creating timers and will be the function passed to
ToggleableTimerForm
• handleFormSubmit() will handle updating timers and will be the function passed to EditableTimer

Both functions travel down their respective component hierarchies until they reach TimerForm as
the prop onFormSubmit().
Let’s start with handleCreateFormSubmit().
Handling creates
handleCreateFormSubmit() will be the function App will supply to ToggleableTimerForm. Let’s set

that prop now:

React Fundamentals

111

time-tracking/3/App.js



Next, we’ll define the function.
For creating timers, we’ll be using the function newTimer() from utils/TimerUtils.js. This just
hides some logic, like generating ids. Here’s what it looks like:
time-tracking/utils/TimerUtils.js

export const newTimer = (attrs = {}) => {
const timer = {
title: attrs.title || 'Timer',
project: attrs.project || 'Project',
id: uuidv4(),
elapsed: 0,
isRunning: false,
};
return timer;
};

The function accepts an object with timer and project properties and returns a new object with the
rest of the properties properly initialized.
Import that function at the top of App.js:
time-tracking/3/App.js

import { newTimer } from './utils/TimerUtils';

Inside handleCreateFormSubmit(), we’ll use newTimer() to insert a new timer object into state.
Declare this function above render():
time-tracking/3/App.js

handleCreateFormSubmit = timer => {
const { timers } = this.state;
this.setState({
timers: [newTimer(timer), ...timers],
});
};

112

React Fundamentals

Note that we set this.state.timers to a new array of timers. The first element in the array is our
new timer, created with newTimer(). Then, we use JavaScript’s spread syntax to add the rest of our
existing timers to this new array. We do this to avoid mutating state.
The tempting alternative is to write handleCreateFormSubmit() like this:
handleCreateFormSubmit = timer => {
const { timers } = this.state;
this.setState({
timers: timers.push(newTimer(timer)), // mutates state!
});
};

But .push() appends the new timer to the existing array in state. It’s subtle, but this mutates
state. And we never want to mutate state outside of the this.setState() method.
We always want to treat the state object (and the objects and arrays inside state) as
immutable. Writing immutable JavaScript can be tricky at first. A simple strategy is to just
avoid using certain Array and Object methods. .push() is one method to avoid, as it always
mutates the array it is called on.
If you still find the distinction between .push() and the spread syntax confusing, don’t
worry. We’ll be showcasing strategies for how to avoid accidental state mutations throughout the book.

Spread syntax
In arrays, the ellipsis (…) will expand the array that follows into the parent array. The spread
operator enables us to succinctly construct new arrays as a composite of existing arrays:
const a = [ 1, 2, 3 ];
const b = [ 4, 5, 6 ];
const c = [ ...a, ...b, 7, 8, 9 ];
console.log(c);

// -> [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

In objects, the ellipsis (…) will allow you to create a modified version of an existing object:
const coffee = { milk: false, cream: false };
const coffeeWithMilk = { ...coffee, milk: true };
console.log(coffeeWithMilk); // -> { milk: true, cream: false }

This can be useful when working with immutable JavaScript objects.

React Fundamentals

113

Try it out
Now we’ve finished wiring up the create timer flow from the form down in TimerForm up to the
state managed in App. Save App.js and your app should reload. Toggle open the create form and
create some new timers:

Updating timers
Our app is setup for creating timers. Let’s add updates next.
We know we’ll eventually want App to define a handler, onFormSubmit(), for when TimerForm
submits a timer update. However, as you can see in the current state of the app, we haven’t yet
added the ability for a timer to be edited. We don’t yet have a way to display an edit form which
will be a prerequisite to submitting one.
To display an edit form, the user will press on the edit button on a Timer. This should propagate an
event up to EditableTimer and tell it to flip its child component, opening the form.
We’ll work from the bottom-up again. We’ll start with Timer, specify the prop-functions that it needs,
then move up the component hierarchy.

Adding editability to Timer
Since we’ll be adding a fair bit of functionality to Timer, we’ll first convert it into a class component:

React Fundamentals

114

time-tracking/4/components/Timer.js

export default class Timer extends React.Component {

EditableTimer manages the state of whether or not the edit form is open. So, we’ll expect Timer to
receive a prop from its parent, onEditPress(). We’ll set the onPress prop on the “Edit” TimerButton

to this prop:
time-tracking/4/components/Timer.js

render() {
const { elapsed, title, project, onEditPress } = this.props;
const elapsedString = millisecondsToHuman(elapsed);
return (

{title}
{project}
{elapsedString}






);
}

Updating EditableTimer
Now we’re prepared to update EditableTimer. Again, it will display either the TimerForm (if we’re
editing) or an individual Timer (if we’re not editing).
Let’s add event handlers for both possible child components. For TimerForm, we want to handle the
form being closed or submitted. For Timer, we want to handle the edit button being pressed:

React Fundamentals

time-tracking/4/components/EditableTimer.js

export default class EditableTimer extends React.Component {
state = {
editFormOpen: false,
};
handleEditPress = () => {
this.openForm();
};
handleFormClose = () => {
this.closeForm();
};
handleSubmit = timer => {
const { onFormSubmit } = this.props;
onFormSubmit(timer);
this.closeForm();
};
closeForm = () => {
this.setState({ editFormOpen: false });
};
openForm = () => {
this.setState({ editFormOpen: true });
};

We pass these event handlers down as props:
time-tracking/4/components/EditableTimer.js

render() {
const { id, title, project, elapsed, isRunning } = this.props;
const { editFormOpen } = this.state;
if (editFormOpen) {
return (

);
}
return (

);
}

Look a bit familiar? EditableTimer handles the same events emitted from TimerForm in a similar
manner as ToggleableTimerForm. This makes sense. Both EditableTimer and ToggleableTimerForm
are just intermediaries between TimerForm and App. App is the one that defines the submit function
handlers.
Like ToggleableTimerForm, EditableTimer doesn’t do anything with the incoming timer. In
handleSubmit(), it just blindly passes this object along to its prop-function onFormSubmit(). It then
closes the form with closeForm().
We pass along our new prop to Timer, onEditPress. The behavior for this function is defined in
handleEditPress, which modifies the state for EditableTimer, opening the form.

Defining handleFormSubmit() in App
Like we did with handleCreateFormSubmit(), the last step with this pipeline is to define a handler
for edit form submits up in App, handleFormSubmit().
For creating timers, we have a function that creates a new timer object with the specified attributes
and we prepend this new object to the beginning of the timers array in state.
For updating timers, we need to hunt through the timers array until we find the timer object that is
being updated. As always, the state object cannot be updated directly. We have to use setState().
Therefore, we’ll use map() to traverse the array of timer objects. If the timer’s id matches that of
the form submitted, we’ll return a new object that contains the timer with the updated attributes.
Otherwise we’ll just return the original timer. This new array of timer objects will be passed to
setState():

React Fundamentals

117

time-tracking/4/App.js

handleFormSubmit = attrs => {
const { timers } = this.state;
this.setState({
timers: timers.map(timer => {
if (timer.id === attrs.id) {
const { title, project } = attrs;
return {
...timer,
title,
project,
};
}
return timer;
}),
});
};

Note that we call map() on timers from within the JavaScript object we’re passing to setState().
This is an often used pattern. The call is evaluated and then the property timers is set to the result.
Inside of the map() function we check if the timer matches the one being updated by comparing
their id attributes. If not, we just return the timer. Otherwise, we use the spread operator again to
return a new object with the timer’s updated attributes.
Remember, it’s important here that we treat state as immutable. By creating a new timers object and
then using the spread operator to populate it, we’re not modifying any of the objects sitting in state.
We pass this method down as a prop inside render() to EditableTimer:
time-tracking/4/App.js

{timers.map(({ title, project, id, elapsed, isRunning }) => (

))}

React Fundamentals

118

As we did with ToggleableTimerForm and handleCreateFormSubmit, we pass down handleFormSubmit
as the prop onFormSubmit. TimerForm calls this prop, oblivious to the fact that this function is entirely
different when it is rendered underneath EditableTimer as opposed to ToggleableTimerForm.
Try it out
Both of the forms are wired up! Save App.js, and after your app reloads, try both creating and
updating timers. You can also press “Cancel” on an open form to close it:

Note that the keyboard might get in the way when you’re typing into an edit form. We’ll address
this at the end of the chapter by using the KeyboardAvoidingView component in App.
The rest of our work resides within the timer. We need to:
• Wire up the “Remove” button
• Implement the start/stop buttons and the timing logic itself

Try it yourself
Feeling ambitious? Before moving on to the next section, see how far you can get wiring up
the “Remove” button by yourself. Move ahead afterwards and verify your solution is sound.

React Fundamentals

119

Deleting timers
Adding the event handler to Timer
As with adding create and update functionality, we’ll work from the bottom-up. We’ll start in Timer
and work our way up to App. In App is where we’ll define the function that removes the targeted
timer from state.
In Timer, we’ll begin by defining the function for handling “Remove” button press events:
time-tracking/5/components/Timer.js

handleRemovePress = () => {
const { id, onRemovePress } = this.props;
onRemovePress(id);
};

We’ve yet to define the function that will be set as the prop onRemovePress(). But you can imagine
that when this event reaches the top (App), we’re going to need the id to sort out which timer is
being deleted. The handleRemovePress() method provides the id to this function.
We use onPress to connect that function to the “Remove” TimerButton:
time-tracking/5/components/Timer.js



Routing through EditableTimer
In EditableTimer, we include onRemovePress in the destructured props in the component’s render
method:

React Fundamentals

120

time-tracking/5/components/EditableTimer.js

render() {
const {
id,
title,
project,
elapsed,
isRunning,
onRemovePress,
} = this.props;

We then pass the function along to Timer:
time-tracking/5/components/EditableTimer.js



Implementing the remove function in App
The last step is to define the function in App that removes the desired timer from the state array.
There are many ways to accomplish this in JavaScript. If you attempted to implement this solution
on your own, don’t sweat it if your solution was not the same.
We add our handler function that we will ultimately pass down as a prop:
time-tracking/5/App.js

handleRemovePress = timerId => {
this.setState({
timers: this.state.timers.filter(t => t.id !== timerId),
});
};

Here, we use the Array filter() method to return a new array without the timer object that has an
id matching timerId.
Finally, we pass down handleRemovePress() as a prop:

React Fundamentals

time-tracking/5/App.js



Array filter()
Array filter() accepts a function that is used to “test” each element in the array. It returns
a new array containing all the elements that “passed” the test. If the function returns true,
the element is kept.

Try it out
Save App.js and reload the app. Now you can delete timers:

121

React Fundamentals

122

Adding timing functionality
Functionality for creating, updating, and deleting is now in place for our timers. The next challenge:
making these timers actually track time.
There are several different ways we can implement a timer system. The simplest approach would
be to have a function update the elapsed property on each timer every second. This is why we’ve
included the timer property isRunning. We can do something like this:
this.setState({
timers: timers.map(timer => {
const { elapsed, isRunning } = timer;
return {
...timer,
elapsed: isRunning ? elapsed + 1000 : elapsed,
};
}),
});

We map through all the timers in state and check the value of isRunning. If isRunning is true, we
can add 1000 milliseconds (or 1 second) to elapsed.
Now, to make this work we’ll need to do this every second. We can use JavaScript’s setInterval()
to execute this function on an interval.
Let’s set up our interval in componentDidMount:
time-tracking/6/App.js

componentDidMount() {
const TIME_INTERVAL = 1000;
this.intervalId = setInterval(() => {
const { timers } = this.state;
this.setState({
timers: timers.map(timer => {
const { elapsed, isRunning } = timer;
return {
...timer,
elapsed: isRunning ? elapsed + TIME_INTERVAL : elapsed,
};
}),

React Fundamentals

123

});
}, TIME_INTERVAL);
}

setInterval() accepts two arguments. The first argument is the function we’d like executed on an
interval. Here, we’re performing the logic to update elapsed. The second argument is the length
of the interval (or the delay between function invocations). We set that to TIME_INTERVAL, 1000

milliseconds.
We also capture the return value of setInterval(), setting the component variable this.intervalId.
This special identifier allows us to stop the interval at any point in the future using JavaScript’s corresponding clearInterval(). We’ll want to cancel (or “clear”) this interval if the timer component
is ever unmounted (deleted). Otherwise, our function will run on indefinitely and cause errors. We
can use the componentWillUnmount lifecycle hook for this:
time-tracking/6/App.js

componentWillUnmount() {
clearInterval(this.intervalId);
}

This is the first time we’re using componentWillUnmount. Like the name suggests, this method fires
right before a component is unmounted or removed. In this example, we use clearInterval() to
cancel the logic that updates our timers.
Using setInterval() when a component mounts and clearInterval() when a component unmounts is a common pattern in React apps that require interval events.
In our version of the app, App will never be unmounted. However, it is still best practice
to clear any intervals when a component unmounts. This “future proofs” our app. There
are many libraries, like “hot reloading” libraries, that would cause even the app’s main
component to be unmounted and re-mounted.

Although this timer implementation works for our purposes, it is not the most accurate.
There is no guarantee that timers will be updated precisely every 1000 milliseconds and we
lose accuracy around starts and stops.
An example of a more precise approach would be defining a separate timer attribute, like
runningSince. We could then derive how long a timer has been running by calculating the
difference between the value of runningSince and the current time. If we saved this value
somewhere, it would also allow our timers to continue “running” even while the app is
closed.

React Fundamentals

124

Add start and stop functionality
With our interval in place, we just need to add the ability to flip the isRunning boolean on a given
timer.
The action button at the bottom of each timer should display “Start” if the timer is paused and
“Stop” if the timer is running. These presses will invoke functions defined in App that will modify
isRunning.

Add timer action events to Timer
We’ll start at the bottom again with Timer.
We’ll anticipate two prop-functions, onStartPress() and onStopPress(). Let’s write the button
press event handlers that will call these functions first:
time-tracking/6/components/Timer.js

handleStartPress = () => {
const { id, onStartPress } = this.props;
onStartPress(id);
};
handleStopPress = () => {
const { id, onStopPress } = this.props;
onStopPress(id);
};

We’re propagating the id property up to App so it knows which timer to start or stop.
Inside render(), we’ll anticipate a renderActionButton() method that conditionally shows the
correct button based on whether the timer is running or has stopped:

React Fundamentals

time-tracking/6/components/Timer.js

render() {
const { elapsed, title, project, onEditPress } = this.props;
const elapsedString = millisecondsToHuman(elapsed);
return (

{title}
{project}
{elapsedString}




{this.renderActionButton()}

);
}

Now let’s set up the JSX rendered within this method:
time-tracking/6/components/Timer.js

renderActionButton() {
const { isRunning } = this.props;
if (isRunning) {
return (

);
}
return (

);
}

We could write this conditional inside of the component’s render() method. But, as we briefly
mentioned in the previous chapter, a common pattern in React is to use helper methods to do this.
Sometimes this helps with code clarity and readability. In renderActionButton(), we specifically
render one button or another based on this.props.isRunning.
Now we’ll need to run these events up the component hierarchy, all the way up to App where we’re
managing state.

Run the events through EditableTimer
In EditableTimer, we’ll need to pass onStartPress and onStopPress to Timer:
time-tracking/6/components/EditableTimer.js

render() {
const {
id,
title,
project,
elapsed,
isRunning,
onRemovePress,
onStartPress,
onStopPress,
} = this.props;
const { editFormOpen } = this.state;
if (editFormOpen) {
return (

);

React Fundamentals

127

}
return (

);
}

We can define a single function that handles these props in App. It should hunt through the state
timers array using map, flipping isRunning when it finds the matching timer:
time-tracking/6/App.js

toggleTimer = timerId => {
this.setState(prevState => {
const { timers } = prevState;
return {
timers: timers.map(timer => {
const { id, isRunning } = timer;
if (id === timerId) {
return {
...timer,
isRunning: !isRunning,
};
}
return timer;
}),
};
});
};

When toggleTimer comes across the relevant timer within its map call, it sets the property isRunning
to the opposite of its value. This means it will stop a running timer and start a stopped timer.

React Fundamentals

128

Finally, we pass this function down to EditableTimer in the render method:
time-tracking/6/App.js



Try it out
Save App.js, wait for the app to reload, and behold: you can now create, update, and delete timers
as well as actually use them to time things!

Again, for this app we won’t add server communication. Without it, our app’s data is ephemeral. If
we reload the app, the timers will reset.

Wrapping App in KeyboardAvoidingView
A behavioral quirk in our app so far has been that the keyboard can get in the way when we edit a
timer. As we saw in the first chapter, we can wrap our app in a KeyboardAvoidingView component

React Fundamentals

to address this.
In App.js, first import the component:
time-tracking/6/App.js

import React from 'react';
import uuidv4 from 'uuid/v4';
import {
StyleSheet,
View,
ScrollView,
Text,
KeyboardAvoidingView,

Next, wrap the ScrollView component with it:
time-tracking/6/App.js

render() {
const { timers } = this.state;
return (


Timers




{timers.map(({ title, project, id, elapsed, isRunning }) => (

))}



);
}

Finally, add this style to the styles object:
time-tracking/6/App.js

timerListContainer: {
flex: 1,
},

Our ScrollView will now accommodate the keyboard when we start typing into text inputs.
To finish up, let’s add PropTypes to our components. As discussed in the previous chapter, PropTypes
are nice to have in place when making additions or changes to a React Native app.

PropTypes
Let’s add PropTypes to each of our components beginning with EditableTimer:
time-tracking/components/EditableTimer.js

export default class EditableTimer extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
project: PropTypes.string.isRequired,
elapsed: PropTypes.number.isRequired,
isRunning: PropTypes.bool.isRequired,
onFormSubmit: PropTypes.func.isRequired,
onRemovePress: PropTypes.func.isRequired,
onStartPress: PropTypes.func.isRequired,
onStopPress: PropTypes.func.isRequired,
};

For this component, all our props are required as we’re expecting App to always set them. Now let’s
take a look at Timer:

React Fundamentals

131

time-tracking/components/Timer.js

export default class Timer extends Component {
static propTypes = {
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
project: PropTypes.string.isRequired,
elapsed: PropTypes.number.isRequired,
isRunning: PropTypes.bool.isRequired,
onEditPress: PropTypes.func.isRequired,
onRemovePress: PropTypes.func.isRequired,
onStartPress: PropTypes.func.isRequired,
onStopPress: PropTypes.func.isRequired,
};

Similarly, we know each of the props for Timer should always be provided by EditableTimer. Let’s
add PropTypes and defaultProps for TimerButton:
time-tracking/components/TimerButton.js

TimerButton.propTypes = {
color: ColorPropType.isRequired,
title: PropTypes.string.isRequired,
small: PropTypes.bool,
onPress: PropTypes.func.isRequired,
};
TimerButton.defaultProps = {
small: false,
};

small is an optional prop with a default value of false. Our other props are all required. We’re also
using ColorPropType for our color prop in order to correctly validate if an appropriate color54 is
passed in. We’ll need to import it at the top of our file from react-native as well.

Our last two components that need prop validations are our form components. Let’s begin with
TimerForm:

54 https://facebook.github.io/react-native/docs/next/colors.html

React Fundamentals

132

time-tracking/components/TimerForm.js

export default class TimerForm extends React.Component {
static propTypes = {
id: PropTypes.string,
title: PropTypes.string,
project: PropTypes.string,
onFormSubmit: PropTypes.func.isRequired,
onFormClose: PropTypes.func.isRequired,
};
static defaultProps = {
id: null,
title: '',
project: '',
};

For this component, we only pass in timer attributes (id, title, and project) if we’re editing
a timer form and not creating one. We’ve added appropriate default values for each. Now for
ToggleableTimerForm:
time-tracking/components/ToggleableTimerForm.js

export default class ToggleableTimerForm extends Component {
static propTypes = {
onFormSubmit: PropTypes.func.isRequired,
};

This component only takes a single required prop, onFormSubmit, which fires when we submit our
timer form.

Methodology review
While building our time-tracking app, we learned and applied a methodology for building React
apps. Again, those steps were:
1. Break the app into components
We mapped out the component structure of our app by examining the app’s working UI. We
then applied the single-responsibility principle to break components down so that each had
minimal viable functionality.
2. Build a static version of the app
Our bottom-level (user-visible) components rendered JSX based on static props, passed down
from parents.

React Fundamentals

133

3. Determine what should be stateful
We used a series of questions to deduce what data should be stateful. This data was represented
in our static app as props.
4. Determine in which component each piece of state should live
We used another series of questions to determine which component should own each piece of
state. App owned timer state data and ToggleableTimerForm and EditableTimer both held state
pertaining to whether or not to render a TimerForm.
5. Hardcode initial states
For the components that own state, we initialized state properties with hardcoded values.
6. Add inverse data flow
We added interactivity by decorating buttons with onPress handlers. These called functions
that were passed in as props down the hierarchy from whichever component owned the
relevant state being manipulated.
If we were planning to add server communication to our application, it would make sense to do it
now given we’ve completed setting up the base of our entire application.

Up next
With the first chapter, we explored the basics of React Native by building a weather app. In this
chapter, we dove deeper into the fundamentals of the React API by creating a more interactive
application with more components. Although we covered a number of important concepts including
a useful pattern for building React Native apps from scratch, we’ve so far only briefly covered each
of React Native’s built-in components, like View and Text. Over the next two chapters, we’ll examine
a number of React Native’s core components in greater detail.

Core Components, Part 1
What are components?
Components are the building blocks of any React Native application. We used components like View
and Text throughout the previous chapters to create the UI for our weather app and our timer app.
Out-of-the-box, React Native includes components for everything from form controls to rich media.
Up to this point, we’ve been using React Native components without fully exploring how they
work. In this chapter, we’ll study the most common built-in React Native components. Just as in
the previous chapters, we’ll build an application as we go. When we come across a new topic, we’ll
deep dive into that topic before we keep building. At the end of the chapter, you should have a solid
foundation of knowledge for using any React Native component – even the ones we don’t cover will
follow many of the same patterns.

UI abstraction
Components are an abstraction layer on top of the underlying native platform. On an iOS device,
a React Native component is ultimately rendered as a UIView. On Android, the same component
would be rendered as an android.view. As React Native expands to new platforms, the same code
should be able to render correctly on more and more devices.
React Native is already supported on the universal Windows platform55 , Apple TV (part of
the main react-native repository56 ), React VR57 , and the web58 .

As you start building complex apps, you’ll likely run into cases where you want to use a feature that
exists on one platform but not the other. Platform-specific components exist for cases like these.
Generally, the component’s name will end with the name of the platform e.g. NavigatorIOS. As
we mentioned in the “Getting Started”, there are several ways to run different code on different
platforms – you will need to do this for platform-specific components.

Building an Instagram clone
In this chapter, we’ll use the most common React Native components to build an app that resembles
Instagram. We’ll build the main image feed with the components View, Text, Image and FlatList.
We’ll also build a comments screen using TextInput and ScrollView.
55 https://github.com/Microsoft/react-native-windows
56 https://github.com/facebook/react-native
57 https://facebook.github.io/react-vr/
58 https://github.com/necolas/react-native-web

Core Components, Part 1

135

To try the completed app on your phone:
• On Android, you can scan this QR code from within the Expo app:

• On iOS, you can navigate to the image-feed/ directory within our sample code folder and build
the app using the same process in previous chapters. You can either preview it using the iOS
simulator or send the link of the project URL to your device.
Our app will have two screens. The first screen is the image feed:

Core Components, Part 1

136

The second screen opens when we tap “3 comments” to display comments for that image:

Project setup
Just as we did in the previous chapters, let’s create a new app with the following command:
$ create-react-native-app image-feed --scripts-version 1.14.0

Once this finishes, navigate into the image-feed directory.
Choose one of the following to start the app:
• yarn start - Start the Packager and display a QR code to open the app on your Android phone
• yarn ios - Start the Packager and launch the app on the iOS simulator
• yarn android - Start the Packager and launch the app on the Android emulator
You should see the default App.js file running, which looks like this:

Core Components, Part 1

137

Now’s a good time to copy over the image-feed/utils directory from the sample code into your
own project. Copy the utils directory into the image-feed directory we just created.

How we’ll work
In this chapter, we’ll build our app following the same methodology as the previous chapter. We’ll
break the app into components, build them statically, and so on. We won’t specifically call out each
step, since it isn’t necessary to follow them exactly. They’re most useful as a reference for when
you’re unsure what to do next.
If at any point you get stuck when building an app of your own, consider identifying which
steps you’ve completed, and following the steps more closely until you’re back on track.

Breaking down the feed screen
We want to start thinking about our app in terms of the different components of our UI. Ultimately
our app will render built-in components like View and Text, but as we learned in the previous chapter,
it’s useful to build higher levels of abstraction on top of these. Let’s start by figuring out how our
main image feed might break down into components.

Core Components, Part 1

138

A good component is generally concise and self-contained. By looking at the screenshot we are trying
to build, we can identify which pieces are reasonably distinct from others and reused in multiple
places. Since we’re only building a couple screens, we won’t be able to make fully informed decisions
about which parts of the screenshots are most reusable as we don’t know what the other screens
in the app will look like. But we can make some pretty good guesses. Here’s one way we can break
down the main feed:

•
•
•
•

Avatar - The profile photo or initials of the author of the image
AuthorRow - The horizontal row containing info about the author: their avatar and their name
Card - The item in the image feed containing the image and info about its author
CardList - The list of cards in the feed

Each of these build upon one another: CardList contains a list of Card components, which each
contain an AuthorRow, which contains an Avatar.

Core Components, Part 1

139

Top-down vs. bottom-up
When it comes to building the UI components of an app, there are generally two approaches: topdown and bottom-up. In a top-down approach, we would start by building the CardList component,
and then we would build the components within the CardList, and then the components within
those, and so on until we reach the inner-most component, Avatar. In a bottom-up approach, we
would start with the innermost components like Avatar, and keep building up higher levels of
abstraction until we get to the CardList. Choosing between these two approaches is mostly personal
preference, and it’s common to do a little of both.
For this app, we’re going to work bottom-up. We’ll start with the Avatar component, and then build
the AuthorRow which uses it, and so on.
Unlike the last chapter, we’ll focus on building one component at a time, testing each one as we go.
We can modify App.js to render just the component we’re currently working on.
As an example, if we were to do this for the Avatar component, we might modify the App.js file to
render just the Avatar:
// Inside App.js
render() {
return ;
}

We might also hardcode different kinds of props for testing:
// Inside App.js
render() {
return ;
}

Isolating individual components like this is a useful technique when working with styles. A
component’s layout can change based on its parent – if we build a component within a specific
parent, we may end up with styles that closely couple the parent and child. This isn’t ideal, since
we want our components to look accurate within any parent for better reusability. We can easily
ensure that components work well anywhere by building components at the top level of the view
hierarchy, since the top level has the default layout configuration.
Now that we have our strategy locked down, let’s start with the Avatar component.

Avatar
Here’s what the Avatar should look like, when rendered in isolation:

Core Components, Part 1

140

For simple apps, it’s easiest to keep all of our components together in a components directory.
For more advanced apps, we might create directories within components to categorize them more
specifically. Since this app is pretty simple, let’s use a flat components directory, just like we did in
the previous chapters.
Let’s create a new directory called components and create a new file within that called Avatar.js.
Our avatar component is going to render the components View and Text. It’s going to use StyleSheet,
and it’s going to validate strings, numbers, and color props with PropTypes. Let’s import these things
at the top of the file. We also have to import React.
We’ll import React in this file, even though we don’t reference it anywhere. Behind-thescenes, babel compiles JSX elements into calls to React.createElement, which reference the
React variable.

Add the following imports to Avatar.js:

Core Components, Part 1

141

image-feed/1/components/Avatar.js

import { ColorPropType, StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';

We import ColorPropType from react-native rather than PropTypes. The PropTypes
package contains validators for primitive JavaScript types like numbers and strings. While
colors in React Native are strings, they follow a specific format that can be validated – React
Native provides a handful of validators like ColorPropType for validating the contents of a
value rather than just its primitive type.

Now we can export the skeleton of our component:
image-feed/1/components/Avatar.js

export default function Avatar({ /* ... */ }) {
// ...
}

Since this component won’t need to store any local state, we’ll use the stateless functional
component style that we learned about in the previous chapter.
What should the props be for our avatar? We definitely need the initials to render. We also probably
want the size and background color to be configurable. With that in mind, we can define our
propTypes like this:
image-feed/1/components/Avatar.js

// ...
export default function Avatar({ size, backgroundColor, initials }) {
// ...
}
Avatar.propTypes = {
initials: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
backgroundColor: ColorPropType.isRequired,
};
// ...

Core Components, Part 1

142

In this app, we’ll make most of our props required using isRequired, since we’ll always pass every
prop. If we wanted to make our component more reusable, we could instead make its props optional
– but it’s hard to know which props should be optional until we actually try to reuse it!
It’s time to render the contents of our Avatar. For the colored circular background, we’ll render a
View. The View is the most common and versatile component. We’ve already used it throughout the
previous chapters, but now let’s take a closer look at how it works and how to style it.

View
There are two fairly distinct things we use View for:
• First, we use View for layout. A View is commonly used as a container for other components. If
we want to arrange a group of components vertically or horizontally, we will likely wrap those
components in a View.
• Second, we use View for styling our app. If we want to render a simple shape like a circle or
rectangle, or if we want to render a border, a line, or a background color, we will likely use a
View.
React Native components aim to be as consistent as possible – many components use similar props
as the View, such as style. Because of this, if you learn how to work with View, you can reuse that
knowledge with Text, Image, and nearly every other kind of component.

Avatar background
Let’s use View to create the circular background for our Avatar:
image-feed/1/components/Avatar.js

// ...
export default function Avatar({ size, backgroundColor, initials }) {
const style = {
width: size,
height: size,
borderRadius: size / 2,
backgroundColor,
}
return (

)
}

Core Components, Part 1

143

// ...

As we saw in previous chapters, we can use the style prop to customize the dimensions and colors
of our View component. Here, we instantiate a new object that we pass to the style prop of our
View. We can assign the size prop to the width and height attributes to specify that our View should
always be rendered as a perfect square. Adding a borderRadius that’s half the size of the width and
height will render our View as a circle. Lastly, we set the background color.
In this style object, the attributes are computed dynamically: width, height, borderRadius, and
backgroundColor are all derived from the component’s props. When we compute style objects
dynamically (i.e. when rendering our component), we define them inline – this means we create
a new style object every time the component is rendered, and pass it directly to the style prop of
our component.
When there are a lot of style objects defined inline, it can clutter the render method, making the
code harder to follow. For styles which aren’t computed dynamically, we should use the StyleSheet
API. We’ll practice this more in the next few sections.
Before that, let’s make sure what we have so far is working correctly.
Try it out
Let’s add our Avatar component to App. We haven’t finished Avatar yet, but it’s useful to test as we
go in case we’ve introduced any errors.
Open up App.js and import our Avatar after our other imports:
image-feed/1/App.js

import Avatar from './components/Avatar';

Next, modify the render function to render an Avatar:
image-feed/1/App.js

// ...
export default class App extends React.Component {
render() {
return (



);
}

Core Components, Part 1

144

}
// ...

For any props we didn’t include, the Avatar will use its defaultProps. We should see a 35px teal
circle in the center of the screen:

Regardless of the size of your screen, the teal circle will render in the center. This means React
Native is calculating the center of the screen, calculating the dimensions of the Avatar, and using
these calculations to properly position the View component. As we learned in the “Getting Started”
chapter, the React Native layout engine is based on the flexbox algorithm. Let’s start digging into
how layout works: how does React Native know the dimensions for each component and where to
render it on the screen?

Dimensions
The first thing we want to think about when understanding the layout of a screen is the dimensions
of each component. A component must have both a non-zero width and height in order to render
anything on the screen. If the width is 0, then nothing will render on the screen, no matter how large
the height is.

Core Components, Part 1

145

In our Avatar example, we rendered our View with fixed dimensions by specifying an exact width
and height as part of the style prop. This is the simplest way to specify component dimensions.
Our View will always render at exactly 35px by 35px, regardless of the screen size or the components
within it.
In the case of Avatar, this is exactly the behavior we want. However, in many cases we want our
layout to automatically adapt to different screen sizes.
Our App renders a View that fills the entire screen, yet we never specified a fixed width or height. If
you look at the StyleSheet.create call at the bottom of App.js, you’ll see the style attribute flex:
1.
image-feed/1/App.js

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

We can use flex to adapt our layout to different screen sizes.

Flex
The flex style attribute gives us the ability to define layouts that can expand and shrink automatically based on screen size. The flex value is a number that represents the ratio of space that a
component should take up, relative to its siblings.
If a component has no siblings, as in the case of the top-level View rendered by App, things are
straightforward:
• with a flex of 1, the component will expand to fill its parent entirely
• with a flex value of 0, the component will shrink to the minimum space possible (just large
enough for the component’s children to be visible, if it has any)
Since the View in App has a flex value of 1, it expands to fill its parent, which in this case is the
entire screen. Now we know why this View expands to fill the screen, but how does React Native
know to render the Avatar (and its underlying View) in the center of the screen?

Core Components, Part 1

146

Layout
We can apply three style attributes to a parent component in order to specify the layout of its
children. That is, we can specify where children render within a parent. The attributes are:
• flexDirection
• justifyContent
• alignItems
With these attributes, we can achieve nearly any kind of layout.
flexDirection

The first attribute is flexDirection. The flexDirection we choose defines the primary axis. Children
components are laid out along the primary axis. The orthogonal axis is called the secondary axis.
The possible values for flexDirection are:
•
•
•
•

column: for a vertical layout (the default)
row: for a horizontal layout
column-reverse: the same as column but flipped vertically
row-reverse: the same as row but flipped horizontally

Core Components, Part 1

147

The names are a little confusing at first, because when you hear “row”, you might think we’ll
get a layout with multiple rows – but in fact, this is saying that our layout is a row.

justifyContent

Next we’ll use the justifyContent attribute to distribute children along the primary axis. The
possible values are:
•
•
•
•
•

flex-start: Distribute children at the start of the primary axis (the default)
flex-center: Distribute children at the center of the primary axis
flex-end: Distribute children at the end of the primary axis
space-around: Distribute children evenly, including space at the edges
space-between: Distribute children evenly, without any space at the edges

The following diagram depicts the possible values for justifyContent in both row and column
layouts. Remember, the flexDirection sets the primary and orthogonal axes, so our choice of
flexDirection will determine the meaning of justifyContent.

Core Components, Part 1

148

alignItems

Lastly, we’ll use the alignItems attribute to align children along the secondary axis. Possible values
are:
•
•
•
•

flex-start: Align children at the start (the default)
flex-center: Align children at the center
flex-end: Align children at the end
stretch: Stretch children to fill the entire width/height of the secondary axis

The following diagram depicts the possible values for alignItems, in both row and column layouts.

Core Components, Part 1

149

Take another look at the container style in StyleSheet.create at the bottom of App.js:
image-feed/1/App.js

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

Let’s figure out how this style centers the Avatar within the View. This style object doesn’t contain
a value for the flexDirection attribute, so instead it’ll use the default value, column. This means the

Core Components, Part 1

150

primary axis is the vertical axis and the secondary axis is the horizontal axis. The justifyContent:
'center' distributes the Avatar to the center of the vertical axis. The alignItems: 'center' aligns
the Avatar in the center of the horizontal axis.

Flex and the primary axis
Now that we know how flexDirection and axes work, let’s revisit how this top-level View uses flex
to fill the entire screen.
The flex attribute of a component determines only its dimension along the primary axis. This means
that, just like for justifyContent and alignItems, we need to know what the flexDirection is in
order to use flex correctly.
In the case of our View in App, it’s best to imagine this View is actually the child of another wrapper
View that fills the entire screen. This wrapper View has the default style attributes for flexDirection,
justifyContent, and alignItems. In other words, the top-level View that we render is actually inside
a parent with style:
{
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'stretch'
}

Our top-level View has a fullscreen height because we specify flex: 1, which stretches it across
the vertical axis. It has a fullscreen width because its parent uses alignItems: 'stretch', which
stretches the View across the horizontal axis.
What to do if a component doesn’t show up
Beginners and experts alike frequently run into the problem where a component doesn’t render
anything on the screen. The most common reason for this is that the component has dimensions
equal to 0.
When using flex: 0 or no flex attribute, a component will only have a dimension greater than
0 along the primary axis if given explicitly (using a width or height attribute) or if its children
have dimensions greater than 0. Similarly, when using alignItems: 'stretch', a child will only
have dimensions greater than 0 along the secondary axis if given explicitly or if the parent has
dimensions greater than 0.
Thus, when a component doesn’t show up on the screen, the first thing we should do is pass an
explicit width and height style attribute (and also a backgroundColor, just to make sure something
is visible). Once a component appears on the screen, we can start understanding the component
hierarchy and evaluating how to tweak our styles.

Core Components, Part 1

151

StyleSheet
Now that we have a better understanding of layouts, lets get back to creating our Avatar component.
As a reminder, here’s what we’re aiming for:

The next thing we’ll want to add is the text within the circular View. The text should be centered,
which we now know how to do using justifyContent and alignItems. Let’s do that now.
We previously used an inline style object for the style prop of View. For styles which don’t need
to be computed dynamically based on props, such as centering the content within the View, we
generally use a StyleSheet at the bottom of the file. Let’s go ahead and update Avatar.js with the
following:

Core Components, Part 1

152

image-feed/1/components/Avatar.js

// ...
export default function Avatar({ size, backgroundColor, initials }) {
const style = {
width: size,
height: size,
borderRadius: size / 2,
backgroundColor,
};
return (

);
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
});

In React Native, styles are most often defined below the component code in the same
file. When reading a file, generally the component is the primary concern, and styles are
secondary – this is why we put the component code first. This works because the variable
name styles is hoisted to the top of the file, and the code which defines its value is executed
before the code that accesses its value. As you may have noticed, we’ve been doing this since
the start of the book, and we’ll continue to do it throughout.

In this case, we always want the text to be centered on both axes, so we’ll use justifyContent:
'center' and alignItems: 'center'. As we saw in the “Getting Started” chapter, we can merge
both of our style objects together by passing an array as the style prop of View.
When centering a single child component within a View like this, any flexDirection will
result in the same layout, so we can use either.

Now that we’ve centered the contents of our View, let’s add the text for the initials. We’ll use Text
for this. We’ve used Text before, but let’s take a step back and look at it in-depth before we add it
to our avatar.

Core Components, Part 1

153

Text
We use the Text component to render text on the screen. Text can be styled with font-specific
attributes such as fontSize. It can use nearly all of the same styles as View, such as backgroundColor
and width. However, Text has some key differences when it comes to layout.

Text dimensions
Unlike the View component, Text components have an intrinsic size. In other words, if we don’t
specify a width or height, a Text component will still show up on the screen. If we were to put a
background color behind it to visualize the width and height, we could see that the background is
exactly the size of the text we see (plus or minus a little space, depending on the line height).
Rendering a Text component with:

Hello World


Gives us:

Core Components, Part 1

154

Specifying a width, height, or flex attribute as part of the style will override the intrinsic dimensions
of the Text. Rendering a Text component with:

Hello World


Gives us:

Text context will automatically wrap around by default when it fills the width of the component.
This is configurable with the numberOfLines prop.

Common Text props and styles
Here are a few common style attributes that you might want to use with text:
• color - A string representing the color of the text.
• fontFamily - A string with the name of the font family (this font family must already exist on
the device).
• fontSize - A number value equal to the size of the font in points.
• fontStyle - Either 'normal' or 'italic'.

Core Components, Part 1

155

• fontWeight - The thickness of each character. One of 'normal', 'bold', '100', '200', '300',
'400', '500', '600', '700', '800', or '900'. If the chosen weight isn’t available on the device,
the nearest available weight will be used instead).
• textAlign - The text alignment. One of 'left', 'right', 'center', 'justify' (iOS only),
'auto'.
In addition, we use the following props frequently:
• numberOfLines - The number of lines to allow before truncating the text.
• ellipsizeMode - How text should be truncated when it exceeds numberOfLines. One of 'head',
'middle', 'tail', 'clip' (iOS only).
You can find the full list of props and styles for Text in the official docs59 .
Like View elements, Text elements can have children. This is useful when you want to have multiple
styles of text within the same paragraph. The Text element will inherit styles from its parent. If the
parent has a fontSize of 16 and color of blue, a child Text element will have the same styles by
default. The child Text element can be styled to override its parent’s styles as needed.

Adding Text to Avatar
Let’s render and style a Text component to display the initials in the Avatar component:
image-feed/1/components/Avatar.js

// ...
export default function Avatar({ size, backgroundColor, initials }) {
const style = {
width: size,
height: size,
borderRadius: size / 2,
backgroundColor,
}
return (

{initials}

);
}
59 https://facebook.github.io/react-native/docs/text.html

Core Components, Part 1

156

// ...
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: 'white',
},
});

After saving Avatar.js, you should see the following:

Since we don’t want our content centered on the screen, let’s update the styles in App.js to
render content starting at the top left. We can do this by removing alignItem and justifyContent.
Since we’re in a View with flexDirection: "column" (the default), we can use justifyContent:
"flex-start" (also the default) to distribute content starting at the top of the screen.
We also want to leave room at the top of the screen for the status bar. We’ll import Constants from
expo so we can use Constants.statusBarHeight.

Core Components, Part 1

The expo library provides a variety of APIs and components beyond those provided by react-native – these are available to us out-of-the-box since we’re using
create-react-native-app to create our app.

Add the following import to the top of App.js:
image-feed/1/App.js

import { Constants } from 'expo';

Then update the container style at the bottom of the file to:
image-feed/1/App.js

// ...
const styles = StyleSheet.create({
container: {
marginTop: Constants.statusBarHeight,
flex: 1,
backgroundColor: '#fff',
},
});

Now we should see our avatar at the top left of the screen, sitting just below the status bar.

157

Core Components, Part 1

158

In order to create the Avatar, we covered View, Text, StyleSheet, and layout with flexbox. These are
the built-in components and APIs required to build nearly any custom component in React Native.
We’ll spend most of the rest of the chapter using them to build other components in our image feed
app.

AuthorRow
Now that we’ve completed the Avatar, let’s move on to the next component! Let’s create the
horizontal row containing our Avatar and the full name of the photo author.

Core Components, Part 1

159

Create a new file AuthorRow.js in our components directory.
In this file, we’ll import mostly things we’ve seen already: StyleSheet, View, Text, PropTypes, and
React. We’ll also import a TouchableOpacity so that we can handle taps on the “Comments” text to
take us to the comments screen. We’ll also need to import the Avatar component we just made, and
a few of the utility functions we copied into this project at the start of the chapter.
If you haven’t copied over the utils directory from our sample code, you should do so now.

image-feed/1/components/AuthorRow.js

import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
import Avatar from './Avatar';
import getAvatarColor from '../utils/getAvatarColor';
import getInitials from '../utils/getInitials';

Now let’s figure out the propTypes for the component. We’ll want to configure the full name we

Core Components, Part 1

160

display next to the Avatar and the text we use for the “Comments” link on the right side. We’ll also
want to propagate press events when the user taps the link.
image-feed/1/components/AuthorRow.js

// ...
export default function AuthorRow({ fullname, linkText, onPressLinkText }) {
}
AuthorRow.propTypes = {
fullname: PropTypes.string.isRequired,
linkText: PropTypes.string.isRequired,
onPressLinkText: PropTypes.func.isRequired,
};
// ...

Thinking about the layout of the component, we’ll want to have a View with flexDirection: 'row'.
Within this we’ll render an Avatar, a Text, and a TouchableOpacity.
Let’s start with the styles for the View and Text. Add this to the bottom of the file:
image-feed/1/components/AuthorRow.js

// ...
const styles = StyleSheet.create({
container: {
height: 50,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 10,
},
text: {
flex: 1,
marginHorizontal: 6,
},
});

We use flex: 1 so that Text expands to fill any remaining space in the View. This will push the
TouchableOpacity to the right side.
Now we can fill out the component function:

Core Components, Part 1

161

image-feed/1/components/AuthorRow.js

// ...
export default function AuthorRow({ fullname, linkText, onPressLinkText }) {
return (



{fullname}

{/* ... */}

);
}
// ...

We’ll use numberOfLines={1} so that the Text is truncated when it reaches the end of the line, rather
than wrapping around to multiple lines.
Now let’s render a TouchableOpacity to add the “Comments” link text and handle taps.

TouchableOpacity
The TouchableOpacity component is similar to View, but lets us easily respond to tap gestures in a
performant way. The TouchableOpacity component fades out when pressed, and fades back in when
released. The opacity animation happens on the native side (it doesn’t trigger a re-render), so the
animation is extremely smooth and the interaction is low latency. The opacity value when pressed
can be configured with the activeOpacity prop by providing a number from 0 to 1.
If you don’t like the opacity animation, you can instead use a TouchableHighlight for a background
color changing animation.
One minor inconvenience with both TouchableOpacity and TouchableHighlight: these components
can only have a single child element, so if we want multiple children, we will need to wrap them in
a View.

Core Components, Part 1

162

Adding TouchableOpacity to AuthorRow
Let’s render a TouchableOpacity for “Comments” to the right of the Text in our AuthorRow. We’ll
use the onPress prop of the TouchableOpacity to call our onPressLinkText prop.
image-feed/1/components/AuthorRow.js

export default function AuthorRow({ fullname, linkText, onPressLinkText }) {
return (



{fullname}

{!!linkText && (

{linkText}

)}

);
}

We use !!linkText to conditionally render the  element. The double negation
with !! lets us make sure we’re dealing with a boolean value.
Since linkText is a string, the && expression would evaluate to a string type when linkText
is the empty string '' – in React Native (unlike on the web), we’re not allowed to render
string values outside of Text (even empty strings).

Try it out
Let’s update App.js to render our AuthorRow component in order to test it:

Core Components, Part 1

image-feed/1/App.js

// ...
import AuthorRow from './components/AuthorRow';
export default class App extends React.Component {
render() {
return (

 {
console.log('Pressed link!');
}}
/>

);
}
}
// ...

Here’s what our AuthorRow should look like:

163

Core Components, Part 1

164

You can also try pressing the “Comments” text. The text’s opacity should animate and you’ll see
“Pressed link!” logged to the terminal.
In our AuthorRow, as in earlier chapters, we used the style attributes paddingHorizontal and
marginHorizontal to adjust the spacing between the different components we rendered. Let’s dive
into how these attributes work.

Padding, margin, borders, and the box model
The React Native layout engine uses what’s known as the box model for customizing spacing. You
might be familiar with the box model if you’ve developed for the web. There are three main style
attributes we can use:
• margin: This is the amount of space between a component and its siblings or the edge of its
parent’s content area.
• border: This is the border drawn around the component, which can vary in width, style (e.g.
a dashed line), and color.
• padding: this is the spacing within a component before its children components.
Each of these style attributes can have a different size on each side of the component: top, right,
bottom, and left. For example, if we wanted to set a top margin of 10 pixels, we would write

Core Components, Part 1

165

marginTop: 10 in the styles object. For convenience, we can set all four sides to have the same
size with margin: 10. We can also set vertical and horizontal margin with marginVertical: 10 and
marginHorizontal: 10. The more specific style attributes will override the more generic ones – if
we write margin: 10, marginTop: 20, the top will have a margin of 20, while the rest of the sides
will have a margin of 10. All of the same rules apply to padding and border too (except that for
border, the attribute is called borderWidth instead of border).

Here is an illustration of the box model:

In this example, there’s a margin of 20, a borderWidth of 10, padding of 20, and a content area of
130 wide by 80 tall. The borderWidth and margin on the bottom side are different than the rest: the
border on the bottom is 20, and the margin on the bottom is 30. Here’s how we might write the style
for this:
{
margin: 20,
borderWidth: 10,
padding: 20,
borderBottomWidth: 20
marginBottom: 30
};

When we use a fixed width or height, this includes the content area, the padding area, and the border
width. Margin is not counted in the width or height, since it is space outside of the component’s
edges. The width in this example is 10 + 20 + 130 + 20 + 10 = 190, and the height is 10 + 20 +
80 + 20 + 20 = 150.

Core Components, Part 1

166

We’ll continue using these spacing style attributes and the box model as we build the rest of the
components in our app.

Card
Next up, we’ll make the card containing AuthorRow and the Image component.

Since rendering Image components will be an important part of our app, let’s look at how images
work in more detail.

Image
We use the Image component to render images on the screen. There are two ways to include images
in an app: we can bundle an image asset with our code (which will then get stored on the device),
or we can download an image from a URI.

Bundling image assets
To bundle an image asset, we can require the image by name from our project directory just like
any other file. The React Native packager will give us a reference to this image (a number) that

Core Components, Part 1

167

represents the image’s metadata. The packager will automatically bundle images for multiple pixel
densities if we name them with the @ suffix: .png for standard resolution, @2x.png for 2x resolution,
and @3x.png for 3x resolution. We can pass an image reference to the source prop of an Image to
render it.
For example, if we had a file called foo.png in the root directory of our app, we could use:


We won’t bundle any images in this app, however, since the images we want to display come from
the web. Instead, we’ll load remote images assets.

Remote image assets
To display an image from a URI, we must pass an object to the source prop of the Image component.
The object should contain a string value uri, and may optionally contain number values for width
and height (representing the image’s intrinsic dimensions, pre-calculated). The Image component
will automatically download the data from the URI and display it once loaded.


Since large images may take a while to download, we’ll often show a loading indicator of some sort
before the download has finished. We can pass a callback function to the onLoad prop of Image to
determine when the image has loaded. We’ll explore this shortly.
In this chapter, we’ll use the open API, https://unsplash.it, to fetch images for our image feed.
This API is very useful for testing apps that need placeholder images. We’ll use two API endpoints:
• https://unsplash.it/${width}/${height}: This endpoint gives us a random image. We can
use the query parameter image and pass the id of an image to fetch a specific image, e.g.
?image=10. We can specify the dimensions of the image by putting the desired width and
height in the URL. We’ll choose an arbitrary size, 600 x 600, for this app.
• https://unsplash.it/list: This endpoint gives us a list of image metadata objects. The
metadata object for each image contains an id which we can use for the image query parameter
of the previous endpoint.
We’ll be using two utility functions in utils/api.js: getImageFromId(id) and fetchImages().
These functions correspond to the two unsplash.it APIs, respectively.

Core Components, Part 1

168

Common image styles
We can use the resizeMode style (or prop – both work) to determine how the image is cropped in
the case where the image data’s intrinsic dimensions are different than the dimensions of the Image
component.
The options for resizeMode are:
• cover: The image scales uniformly to fill the Image component. The image will be cropped by
the bounding box of the component if they have different aspect ratios.
• contain: The image scales uniformly to fit within the component. The component’s background
color will show if they have different aspect ratios.
• stretch: The image stretches to fill the component.
• repeat: The image repeats itself at its intrinsic dimensions to fill the component (iOS-only).
• center: The image maintains its intrinsic dimensions, and is centered within the component.
We can use the aspectRatio style to render the image at a specific aspect ratio, regardless of its
intrinsic dimensions. We provide a number value which represents the ratio of width to height. For
example, if we set aspectRatio: 2, this means the ratio of width to height is 2 to 1 – the image will
render twice as wide as it is tall.
While most commonly used with images, the aspectRatio style can be used on any component,
such as View or Text.
If you’re coming from the web, you’ll likely find this style surprising since there’s no
equivalent style in CSS. The React Native layout engine, Yoga, added this style to its flexbox
implementation.

Yoga
React Native uses the Yoga layout engine (also from Facebook). This is a cross-platform implementation of the flexbox algorithm. It matches the algorithm used by web browsers pretty closely, but
with two important differences:
• The default values are different
• Yoga adds a couple new features that don’t exist in the browser (like aspectRatio)
If you want to read more about the algorithm and all of the styles available to use, check out the
Yoga docsa .
a https://facebook.github.io/yoga/

Core Components, Part 1

169

Adding Image to Card
Let’s set up the outline for our Card component and render an Image.
Create a new file Card.js in the components directory. Add the following to this file:
image-feed/1/components/Card.js

import { Image, StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
import AuthorRow from './AuthorRow';
export default class Card extends React.Component {
static propTypes = {
fullname: PropTypes.string.isRequired,
image: Image.propTypes.source.isRequired,
linkText: PropTypes.string,
onPressLinkText: PropTypes.func,
};
static defaultProps = {
linkText: '',
onPressLinkText: () => {},
};
// ...
render() {
// ...
}
}

Most of the props we use here should look familiar: fullname, linkText, and onPressLinkText
will all get passed into the AuthorRow we created earlier. The interesting one is image – we use
Image.propTypes.source as the type, so that we can pass this directly into the source prop of the
Image we’ll render.
Let’s fill out the component function:

Core Components, Part 1

170

image-feed/1/components/Card.js

// ...
render() {
const { fullname, image, linkText, onPressLinkText } = this.props;
return (




);
}
// ...
const styles = StyleSheet.create({
image: {
aspectRatio: 1,
backgroundColor: 'rgba(0,0,0,0.02)',
},
});

We’ll render a View (with the default style flexDirection: 'column') in order to vertically stack
our AuthorRow and Image component. Since the View style defaults to alignItems: 'stretch', the
image stretches horizontally to fill the screen. We use aspectRatio: 1 to make the height of the
Image match its full-screen width, rendering as a perfect square. We put a backgroundColor on the
Image which will show before the image loads, or behind the image if the image is transparent.
Try it out
To test this, let’s render our new Card from App. Update the component in App.js to the following:

Core Components, Part 1

171

image-feed/1/App.js

// ...
import Card from './components/Card';
export default class App extends React.Component {
render() {
return (

 {
console.log('Pressed link!');
}}
image={{ uri: 'https://unsplash.it/600/600' }}
/>

);
}
}
// ...

When you save App.js, you should see our AuthorRow from earlier plus a random image on your
device:

Core Components, Part 1

172

Loading status
You might notice that we see the background color behind our image as we wait for it to load. Let’s
add a loading indicator before the image has fully loaded, to provide more feedback to the user.
We can pass a callback to the onLoad prop of Image in order to monitor the loading status. Let’s keep
track of the Image loading status in the state of our Card component. Update Card.js to include the
following:
image-feed/1/components/Card.js

export default class Card extends React.Component {
// ...
state = {
loading: true,
};
handleLoad = () => {
this.setState({ loading: false });
};

Core Components, Part 1

173

render() {
const { fullname, image, linkText, onPressLinkText } = this.props;
const { loading } = this.state;
return (




);
}
}

Now we’re tracking when the image has fully loaded with state.loaded. Next we’ll render the
loading indicator when state.loaded is true.

ActivityIndicator
We can render a loading indicator using the ActivityIndicator component.

Core Components, Part 1

174

This component accepts all the same props as View, plus a few more:
• animating: A bool indicating whether to show or hide the indicator (defaults to true).
• color: The color of the spinner (defaults to gray).
• size: One of 'small' or 'large' (defaults to small).
We want to position the ActivityIndicator in the center of the image. Unlike View, the Image
component doesn’t accept a children prop, so we can’t put the ActivityIndicator inside it. We
could use the ImageBackground component like we did in the “Getting Started” chapter, but let’s
instead look at a more generic way we can stack components: position.

Position
So far we’ve mostly used the style attributes flexDirection, justifyContent, and alignItems in our
layouts. React Native gives us another powerful style attribute we can use to adjust layout: position.
Position can be either 'relative' or 'absolute'.
relative

When set to 'relative' (which is the default), we’re able to tweak the position of a component after
it has already been laid out according to its flex, width, height, etc. We can use a combination of

Core Components, Part 1

175

top, right, bottom, and left. For example, if we want to move a component down on the screen by
20 pixels, we could say top: 20 to indicate that its top should be 20 pixels greater than it is currently.
Unlike specifying padding, margin, or borderWidth, the style attributes like top don’t affect other
elements in the layout. In other words, adding top: 20 will change the position of the element it

was applied to, but not the position of the element’s siblings or parent (even if this causes them to
overlap).
absolute

When set to 'absolute', the layout of the parent and the component’s flex style are completely
ignored. Instead we use top, right, bottom, and left to specify exactly how the component should
be placed within its parent. For example, if we want the component to be 10 pixels from the bottom
and 20 pixels from the right side of its parent, we can say bottom: 20, right: 10. As always, we
need to make sure the component has dimensions greater than 0. Since flex is ignored, we may
need to specify a fixed width and height. We can also ensure the component has dimensions greater
than 0 by specifying both top and bottom, or both left and right. For example, if we say left: 10,
right: 10, the component will stretch horizontally to fill the space from 10 pixels from the left of its
parent to 10 pixels from the right. Components positioned with position: 'absolute' don’t affect
the layout of their siblings or parent components.
It’s common to use position: 'absolute' to make elements overlap. Suppose we want two sibling
elements to overlap, filling their parent completely. We can add a style like this to both siblings:
const absoluteFillStyle = {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
};

This style will cause an element to fill its parent completely, since its top will match its parent’s top,
its right side will match its parent’s right side, and so on. This technique is so common that there’s a
predefined style to do the same thing: StyleSheet.absoluteFill. This value can be passed directly
to the style prop of an element. Alternately, you can use ...StyleSheet.absoluteFillObject,
copying each of these properties into another style – this is useful if you want to override one
or two properties but keep the rest.
This overlapping behavior is exactly what we want for positioning our ActivityIndicator in the
center of our Image.

Adding ActivityIndicator to Card
We’ll use StyleSheet.absoluteFill to ensure that our ActivityIndicator matches the dimensions
of our Image. To do this, we’ll need to create a common ancestor View for both.

Core Components, Part 1

176

First, in Card.js, make sure to import ActivityIndicator:
image-feed/1/components/Card.js

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

Then, update the render method of Card to the following:
image-feed/1/components/Card.js

render() {
const { fullname, image, linkText, onPressLinkText } = this.props;
const { loading } = this.state;
return (



{loading && (

)}



);
}

If you save Card.js and look at your device, you should see the ActivityIndicator positioned in
the center of the Image before it loads.
It can be a little confusing to see how this works at first, so let’s walk through it. We moved the
styles.image style to the inner View that’s the parent of the Image. This inner View is inside a
parent View with alignItems: 'stretch' (the default) and it has aspectRatio: 1, so we know the
inner View will have the same width as its parent (in this case, the width of the screen) and a height
equal to its width. The Image and ActivityIndicator will have the same top, right, bottom, and
left of the inner View – in other words, they’ll will match the dimensions of the View.

Core Components, Part 1

177

The order we render components in our code matters here: within the inner View, we render the
ActivityIndicator before the Image. The component rendered last in the code will render on top of
its siblings visually. This normally isn’t something we have to think about, since components don’t
stack on top of one another. With position however, sibling components may overlap, and the order
we render them determines their order on screen. In this case, our image Image renders on top of
our ActivityIndicator. The onLoad event may get called after the image has been drawn, so this
way the Image covers up the ActivityIndicator even if both are rendered at the same time.
If you’re coming from the web, you may be wondering about the zIndex style attribute.
React Native has a attribute zIndex, but it behaves a little differently and can have
somewhat unpredictable effects. It’s generally safer to render components in the correct
order, if possible.

There are many other ways to achieve the same layout. Another approach would be to set the Image
style to flex: 1 to fill the View completely.

{loading && (

)}



We could also leave styles.image on the Image, and rely on the fact that its parent View will resize
along the vertical axis to contain its children:

{loading && (

)}



All of these approaches are reasonable, so deciding which to use mostly comes down to preference.
We chose our approach for this chapter because it’s easier to understand at first glance: both children

Core Components, Part 1

178

of the View have StyleSheet.absoluteFill, so it’s clear that they will overlap completely just by
reading the code.

Now that we have our Card component, we can render a list of these to create the main feed.

CardList
The CardList component will render the infinitely scrolling list of authors and images.

We’ll render this list of cards using the FlatList component.

FlatList
FlatList components are used for rendering large quantities of scrollable content. Instead of
rendering a children prop, the FlatList renders each item in an input data array using the
renderItem prop. The renderItem prop is a function which takes an item from the data array and
maps it to a React Element. Each item in data should be an object with a unique id, so that React

can determine when items have been rearranged.

Core Components, Part 1

179

The FlatList is generally performant: it only renders the content on screen (clipping offscreen
content), and only updates rows that have changed. The FlatList is built using a more generic
component, the ScrollView, which we’ll use later in the chapter.

Adding FlatList to CardList
Let’s create a new file, CardList.js, in our components directory. We’ll import the FlatList, our
Card, a utility for building an image url from an id, and a few other things at the top of the file:
image-feed/1/components/CardList.js

import { FlatList } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
import { getImageFromId } from '../utils/api';
import Card from './Card';

Ultimately we’ll use https://unsplash.it to fetch the data for our feed, but for now let’s pretend
our data looks like:
[
{ id: 0, author: "Bob Ross" },
{ id: 1, author: "Chuck Norris" }
];

It’s important that the id field is unique, since we’ll use it to determine the identity of each card in
the feed. If it weren’t unique, we would start to see quirky behavior where some items don’t render.
Fortunately the API we’ll call later has unique id values (as most APIs should!).
We’ll need to provide a function to the FlatList which maps each element in our data array to its
unique key. Let’s define a utility function to do this at the top of the file, which we’ll then pass to the
FlatList as the keyExtractor prop. Our function, keyExtractor, will take an item from our array
and return the id for that item as a string. Define this function below the imports:
image-feed/1/components/CardList.js

const keyExtractor = ({ id }) => id.toString();

We’ll use this function in a moment when we render the FlatList.
Moving on to the propTypes, we’ll want to ensure our input data matches the format we defined
above. We can use a combination of PropTypes.arrayOf and PropTypes.shape. Let’s set up our
component skeleton as follows:

Core Components, Part 1

180

image-feed/1/components/CardList.js

// ...
export default class CardList extends React.Component {
static propTypes = {
items: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
author: PropTypes.string.isRequired,
}),
).isRequired,
};
render() {
// ...
}
}

We’ll use a class component instead of a functional component, since we’ll add a few methods which
need to access props when we add commenting later.
So far we’ve seen how we can validate primitive values with PropTypes.bool, PropTypes.string,
etc. We can use PropTypes.shape() to validate an object, passing the keys of the values we want to
validate. We can use PropTypes.array() to validate an array, passing the type of the element.
If we were planning to use this items data structure in multiple places, we might want to define its
type in a separate file, such as ItemsPropType.js. That way we can define it once and import it from
multiple files, rather than defining it in multiple places. React Native exports a few built-in types
this way, such as ViewPropTypes.

Now let’s render the FlatList:

Core Components, Part 1

181

image-feed/1/components/CardList.js

renderItem = ({ item: { id, author } }) => (

);
render() {
const { items } = this.props;
return (

);
}

Destructuring assignments, revisited
In the previous chapter, we covered destructuring assignments. Destructuring assignments can also
be nested, as in the example above:
renderItem = ({ item: { id, author } }) => {}

This is equivalent to:
renderItem = (obj) => {
const id = obj.item.id;
const author = obj.item.author;
}

We provide the prop keyExtractor to instruct the FlatList how to uniquely identify items – this
helps the FlatList determine when it needs to re-render items as they go in and out of the visible
portion of the screen.
Each time the FlatList decides to render a new item, it will call the renderItem function we provide

Core Components, Part 1

182

it with, with an object as a parameter. The object contains some rendering metadata, along with the
item from the array we passed as the data prop.
Within renderItem, we can then return a Card component based on the item. We can use the item’s
id to construct a URI for an image, leveraging our getImageFromId() utility function.
Save CardList.js and let’s test it out.
Try it out
Update App.js to render the CardList using the hardcoded data we mentioned earlier:
image-feed/1/App.js

// ...
import CardList from './components/CardList';
const items = [
{ id: 0, author: 'Bob Ross' },
{ id: 1, author: 'Chuck Norris' },
];
export default class App extends React.Component {
render() {
return (



);
}
}
// ...

Save App.js and you should see this:

Core Components, Part 1

183

Now that we’ve finished our last custom UI component for the image feed, we can use it to create
our Feed screen.

Adding a screen
Our app will have two screens:
• Feed: The image feed
• Comments: The list of comments for a specific image
In React Native, screens are components just like any other. However, it’s useful to think about
screens slightly differently. Screens are components that fill the entire device screen. They often
handle non-visual concerns, like fetching data and handling navigation to other screens.
It’s common to keep all of the screen components in an app together in a screens directory. For more
advanced apps, we might create directories within screens to categorize them more specifically.
Since this app is pretty simple, let’s use a flat screens directory.
Create a new directory called screens within our top level image-feed directory, and create a new
file within screens called Feed.js.

Core Components, Part 1

184

The Feed screen
This screen will fetch live data from https://unsplash.it and pass the data into our CardList. The
data we fetch will follow the same format as the hardcoded list we’re using currently.
Now that we’re fetching remote data asynchronously, we need to consider loading and error states.
This screen will show a simple loading indicator and error status.
Add the following imports to Feed.js:
image-feed/1/screens/Feed.js

import {
ActivityIndicator,
Text,
ViewPropTypes,
SafeAreaView,
} from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
import { fetchImages } from '../utils/api';
import CardList from '../components/CardList';

Often screens are configured with props just like other components. In this case, we’ll allow a style
prop, which we’ll use for the top level View within this screen. This allows a lot of flexibility for our
screen to be styled however we need. The type of this prop will be the same as the style prop of
View – React Native provides this validator as a separate import, ViewPropTypes.
You might be tempted to use PropTypes.object to represent a style, but this doesn’t work
very well. Styles created with StyleSheet.create are represented as numbers, so this will
cause a warning. Also, ViewPropTypes.style provides in-depth type-checking of each key
and value, which is very valuable.
image-feed/1/screens/Feed.js

// ...
export default class Feed extends React.Component {
static propTypes = {
style: ViewPropTypes.style,
};
static defaultProps = {
style: null,

Core Components, Part 1

185

};
// ...
}
// ...

It’s common to allow a style prop for creating extremely flexible custom components. We could
technically use this style prop however we want, such as styling a deeply nested component –
however, when naming a prop the same as a built-in View prop, we’ll generally try to keep the
behavior similar. Following built-in component conventions makes it easier for other developers to
understand how to use our custom components without reading the source code.
We’ll keep track of three things in the state of our Feed: loading, error, and items.
image-feed/1/screens/Feed.js

state = {
loading: true,
error: false,
items: [],
};

We can use these to decide what to render. We’ll fetch data in componentDidMount, updating
component state when we get a response.
image-feed/1/screens/Feed.js

async componentDidMount() {
try {
const items = await fetchImages();
this.setState({
loading: false,
items,
});
} catch (e) {
this.setState({
loading: false,
error: true,
});
}
}

Core Components, Part 1

186

We made componentDidMount an async function so that we can use the await syntax within it. This
means the function will return a promise. React doesn’t use the return value of componentDidMount
for anything, so this is safe.
Now, let’s update our render method to make use of this new state:
image-feed/1/screens/Feed.js

render() {
const { style } = this.props;
const { loading, error, items } = this.state;
if (loading) {
return ;
}
if (error) {
return Error...;
}
return (



);
}

We’re ready to render our Feed screen from App!

Adding Feed to App
Let’s update App.js to render our new screen. First we’ll need to update the imports at the top of
the file:
image-feed/1/App.js

import { Platform, StyleSheet, View } from 'react-native';
import { Constants } from 'expo';
import React from 'react';
import Feed from './screens/Feed';

Then we can render our Feed within a wrapper View:

Core Components, Part 1

187

image-feed/1/App.js

export default class App extends React.Component {
render() {
return (



);
}
}

Since our Feed uses a SafeAreaView at the top level, we’ll also need to update our styles from before.
We only want to add a marginTop on Android, or on iOS versions less than 11, since the top margin
is added automatically by the SafeAreaView on iOS 11+ now.
We can use Platform.Version to detect the native operating system version. On iOS, this is a string
like '10.3', while on Android it’s a number.
image-feed/1/App.js

const platformVersion =
Platform.OS === 'ios' ? parseInt(Platform.Version, 10) : Platform.Version;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
feed: {
flex: 1,
marginTop:
Platform.OS === 'android' || platformVersion < 11
? Constants.statusBarHeight
: 0,
},
});

And with that, we’re finished with the feed! Here’s the final result with live data:

Core Components, Part 1

List Performance
If you’re running a version of React Native greater than 0.55, you may see the following warning
after scrolling through a hundred or so images:
1
2
3

VirtualizedList: You have a large list that is slow to update - make sure your
renderItem function renders components that follow React performance best practices
like PureComponent, shouldComponentUpdate, etc.

React Native is letting us know that our list is taking a long time to render once it gets to a certain
length. We can address this by reducing the amount of cards we re-render.
The FlatList re-renders our cards while we scroll. Most of the time, however, a card’s data
doesn’t change, so we don’t need to update the component after the initial render – the only
time the data might change is if the number of comments to display changes. We can add a
shouldComponentUpdate method to Card.js to reduce re-renders and thus improve the performance
of our FlatList. We could add the following shouldComponentUpdate method:
shouldComponentUpdate(nextProps) {
return this.props.linkText !== nextProps.linkText
}

188

Core Components, Part 1

189

Then our cards would only re-render if absolutely necessary. You may still get the same warning at
a certain point, but it should take several times more scrolling than before.
Most of the time using shouldComponentUpdate is a premature optimization. Even when it comes to
large lists like this, often the performance will be good enough in the production build without any
other optimizations. However, if you get a warning, it’s an optimization worth considering.

What we’ve built so far
Let’s recap what we’ve done so far. We used the components View, Text, Image, and FlatList to build
a cross-platform, infinitely scrolling list of images and authors. We created 4 components, each one
building on top of the previous: Avatar, AuthorRow, Card, and CardList. We tested each component
as we built it, by rendering from our top-level component, App. We used a variety of techniques for
layout:
•
•
•
•
•
•

Setting the width and height explicitly
Using flex to stretch elements
Using flexDirection, justifyContent, and alignItems for children layout
Using padding and margin to define spacing between elements
Using position: 'absolute' to stack elements on top of one another
Creating optimized styles with StyleSheet.create

These are the fundamental building blocks of any React Native UI. We’ll continue to use these
throughout the rest of the book, as we add more components and APIs to our repertoire.

Core Components, Part 2
Picking up where we left off
We successfully built an awesome infinitely-scrolling image feed. Next, we’re going to add a new
screen to the same app for commenting on images.
This is a code checkpoint. If you haven’t been coding along with us but would like to start
now, we’ve included a snapshot of our current progress in the sample code for this book.
If you haven’t created a project yet, you’ll need to do so with:
$ create-react-native-app image-feed --scripts-version 1.14.0

Then, copy the contents of the directory image-feed/1 from the sample code into your new
image-feed project directory.

Comments
Here’s what the comments screen will look like:

Core Components, Part 2

191

To build this portion of the app, we’ll learn how to use the TextInput, ScrollView, and Modal
components. We’ll also cover a few other topics like AsyncStorage. We’ll make a few assumptions
so we can focus on built-in components:
• we won’t use a navigation library even though we have multiple screens (more on navigation
in later chapters)
• we only want to store comments locally on the device, rather than remotely via an API
• comments can be saved as simple strings (no id, author, or other metadata)
• the comment input field is at the top of the screen, to avoid complexities around keyboard and
scrolling (which we’ll cover in the next chapter)
• there are few enough comments that a ScrollView will be performant enough (rather than
using a FlatList)

Breaking down the comments screen
The first thing we’ll want to do is break the screen down into components. Here’s one way we can
break it down:

Core Components, Part 2

192

• NavigationBar - A simple navigation bar for the top of the screen with a title and a “close”
button
• CommentInput - The input field for adding new comments
• CommentList - The scrollable list of comments
The App component will be responsible for handling comment data in our app, since both the Feed
screen and Comments screen need to render this data. We’ll render the Comments screen component
from App, passing the comment data for the selected card as a prop. We’ll render the built-in Modal
component to open and close this new screen based on the state of App.
We’ll continue building bottom-up, starting with the CommentInput component, working our way
up to the screen component. We won’t test every component individually by rendering it from App
like we did in the first half of the chapter, but you’re welcome to continue to do this if you liked
having a quicker feedback loop while developing.

CommentInput
First, let’s create the input field for new comments.

Core Components, Part 2

193

TextInput
As we saw in the “Getting Started” chapter, we can use a TextInput component to create an editable
text field for the user to type in.
When working with TextInput, we’ll generally use the following props to capture user input:
• value - The current text in the input field.
• onChangeText - A function called each time the text changes. The new value is the first
argument.
• onSubmitEditing - A function called when the user presses the return/next key to submit/move
to the next field.
It’s common to store the current text in the state of the component that renders the TextInput.
Each time the function we pass to onChangeText is called, we call setState to update the current
text. When the user presses return, the function we passed to onSubmitEditing is called – we can
then perform some action with the current text, and use setState to reset the current text to the
empty string.

Core Components, Part 2

194

Common TextInput props and styles
When working with TextInput, we can use most of the same styles as Text (which includes the
styles for View). A few styles don’t work quite as well as they do on Text though: borders tend not to
render correctly, and padding and line height can conflict in unusual ways. If you’re having trouble
styling a TextInput, you may want to wrap the TextInput in a View and style the View instead.
A few other common props:
• autoCapitalize - For capitalizing characters as they’re typed. One of 'none', 'sentences',
'words', 'characters'.
• autoCorrect - Enable/disable auto-correct.
• editable - Enable/disable the text field.
• keyboardType - The type of keyboard to display. Cross-platform values are 'default',
'numeric', 'email-address', 'phone-pad'.
• multiline - Allow multiple lines of input text.
• placeholder - The text to show when the text field is empty
• placeholderTextColor - The color of the placeholder text
• returnKeyType - The text of the return key on the keyboard. Cross-platform values are 'done',
'go', 'next', 'search', 'send'.
Many more props are available in the docs for TextInput60

Adding TextInput to CommentList
While we could render a TextInput component directly from our Comments screen, it’s often better
to create a wrapper component that encapsulates state, styles, edge cases, etc, and has a smaller
API. That’s what we’ll do in our CommentInput component. The result will be very similar to our
TextInput wrapper components from previous chapters.
Create a new file, CommentInput.js, in the components directory. We’ll import the usual React,
PropTypes, etc, along with the TextInput component:
image-feed/components/CommentInput.js

import { StyleSheet, TextInput, View } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';

We want this component to have two props:
• onSubmit - we’ll call this with the comment text when the user presses the “return” key.
• placeholder - a passthrough to the placeholder prop of the TextInput.
Add the following to CommentInput.js:
60 https://facebook.github.io/react-native/docs/textinput.html

Core Components, Part 2

195

image-feed/components/CommentInput.js

// ...
export default class CommentInput extends React.Component {
static propTypes = {
onSubmit: PropTypes.func.isRequired,
placeholder: PropTypes.string,
};
static defaultProps = {
placeholder: '',
};
// ...
}
// ...

We’ll add a text value to state and methods for updating this value when the value of the TextInput
changes:
image-feed/components/CommentInput.js

state = {
text: '',
};
handleChangeText = text => {
this.setState({ text });
};
handleSubmitEditing = () => {
const { onSubmit } = this.props;
const { text } = this.state;
if (!text) return;
onSubmit(text);
this.setState({ text: '' });
};

We don’t want to allow empty comments, so when handleSubmitEditing is called, we’ll return
immediately if state.text is empty.

Core Components, Part 2

196

Last, we’ll render the TextInput. We want to add a border on the bottom, but adding borders to
TextInput can be a bit unreliable as sometimes they don’t show up. So we’ll wrap the TextInput in
a View and style the View instead:
image-feed/components/CommentInput.js

render() {
const { placeholder } = this.props;
const { text } = this.state;
return (



);
}

image-feed/components/CommentInput.js

const styles = StyleSheet.create({
container: {
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: 'rgba(0,0,0,0.1)',
paddingHorizontal: 20,
height: 60,
},
input: {
flex: 1,
},
});

This is where we pass our state management methods handleChangeText and handleSubmitEditing
to the TextInput, to keep track of changes to the value.
We can use StyleSheet.hairlineWidth as the border width to render the thinnest possible line on
any given device. On a retina device for example, this would be less than 1.

Core Components, Part 2

197

If you want to see what this component looks like to check your work, consider rendering
it from within App for testing.

CommentList
Next, we’ll render a list of comments for each image:

We’ll render these comments in a ScrollView. In reality, we’d probably want to use a FlatList for
performance, but let’s use a ScrollView for practice.

ScrollView
The ScrollView is simpler than the FlatList: it will render all of its children in a vertically or
horizontally scrollable list, without the additional complexity of the keyExtractor or renderItem
props.
The ScrollView is well suited for scrolling through small quantities of content (fewer than 20 items
or so). Content within a ScrollView is rendered even when it isn’t visible on the screen. For large
quantities of items, or cases where many children of the ScrollView are offscreen, you will likely
want to use a FlatList component for better performance.

Core Components, Part 2

198

ScrollView dimensions and layout
You can think of a ScrollView as two separate views, one inside the other. The outer view has a
bounded size, while the inner view can exceed the size of the outer view. If the inner view exceeds
the size of the outer view, only a portion of it will be visible. When we pass children elements to the
ScrollView, they are rendered inside this inner view. We call the inner view the “content container
view”, and can style it separately from the outer view.
Debugging a ScrollView
While building an app, it’s common to render a ScrollView but see nothing on the screen. There
are two common causes for this, based on how the outer view and the content container view work
(assuming vertical scrolling):
• The content container view has flex: 0 by default, so it starts with a height of 0 and expands
to the minimum size needed to contain its children elements. If a child has flex: 1, this child
won’t be visible, since the content container has an intrinsic height of 0. While we could set
the contentContainerStyle to flex: 1, this probably isn’t what we want, since then we’ll
never have content larger than the outer view. Instead, we should make sure the children we
pass to the ScrollView have intrinsic height values greater than 0 (either by using an explicit
height, or by containing children that have height greater than 0).
• The outer view does not change size based on the content container view. In addition to
ensuring that the children of a ScrollView have non-zero height, we have to make sure our
ScrollView has non-zero dimensions – a fixed width and height, flex: 1 and a parent with
alignItems: stretch, or absolute positioning.
Most likely, if the ScrollView doesn’t appear, we need to add flex: 1 to each parent and to the
ScrollView itself. To debug, you can try setting a background color on each parent to see where
flex: 1 stopped getting propagated down the component hierarchy.

Adding ScrollView to CommentList
Let’s render a ScrollView that contains a list of comments. We’ll call this component CommentList.
Create a file CommentList.js in the components directory.
This component will take an items array prop of comment strings, mapping these into View and
Text elements. We’ll set up the outline for this component in CommentList.js as follows:

Core Components, Part 2

199

image-feed/components/CommentList.js

import { ScrollView, StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
export default class CommentList extends React.Component {
static propTypes = {
items: PropTypes.arrayOf(PropTypes.string).isRequired,
};
// ...
}

Unlike FlatList, we don’t need to deal with the keyExtractor and data props. We can simply render
the children of the ScrollView as we would for a View:
image-feed/components/CommentList.js

renderItem = (item, index) => (

{item}

);
render() {
const { items } = this.props;
return {items.map(this.renderItem)};
}

image-feed/components/CommentList.js

const styles = StyleSheet.create({
comment: {
marginLeft: 20,
paddingVertical: 20,
paddingRight: 20,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: 'rgba(0,0,0,0.05)',
},
});

Core Components, Part 2

200

Since comments are stored as strings, we don’t have a convenient value to use as the unique
React key. Using the comment text as the key wouldn’t work, since comments don’t have to
be unique. Using the index as the key works here, but is generally a pattern to be wary of,
since it can cause problems when rearranging items. A better solution would be to augment
our comment data with ids: we could store comments as objects, and use the uuid library
from the previous chapter to assign each comment a unique id for use as the key.

Now that we have a scrolling list of comments, we can move on to the navigation bar, which will
be the last component we make before assembling our comments screen.

NavigationBar
Since our comments screen is going to open in a modal, we want to render a navigation bar with a
title and close button.

In a real app, we would likely use a navigation library for this, but for simplicity, let’s write something
small of our own.
Create NavigationBar.js in the components directory and add the following outline:

Core Components, Part 2

201

image-feed/components/NavigationBar.js

import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
export default function NavigationBar({ title, leftText, onPressLeftText }) {
// ...
}
NavigationBar.propTypes = {
title: PropTypes.string,
leftText: PropTypes.string,
onPressLeftText: PropTypes.func,
};
NavigationBar.defaultProps = {
title: '',
leftText: '',
onPressLeftText: () => {},
};
// ...

We won’t use isRequired on our props, since this component would likely be used without
some of them, e.g. leftText and onPressLeftText, if we were to add more screens to this
app.

This component will be fairly straightforward, using only concepts we’ve covered already. We’ll use
a TouchableOpacity for the close button on the left. We’ll position it with position: 'absolute',
since we don’t want the text on the left to push the title off-center (remember, using position:
'absolute' means the component no longer affects other siblings in the layout). A real navigation
library takes into account many more cases such as text on the right, icons on either side, and long
text that may bump into the title. Let’s keep things simple and just handle the one case at hand.
The component function and styles should look like this:

Core Components, Part 2

202

image-feed/components/NavigationBar.js

export default function NavigationBar({ title, leftText, onPressLeftText }) {
return (


{leftText}

{title}

);
}

image-feed/components/NavigationBar.js

const styles = StyleSheet.create({
container: {
height: 40,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: 'rgba(0,0,0,0.1)',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontWeight: '500',
},
leftText: {
position: 'absolute',
left: 20,
top: 0,
bottom: 0,
justifyContent: 'center',
},
});

Despite generally representing a numeric value, fontWeight must be a string!

We now have all of the building blocks we need: CommentInput, CommentList, and NavigationBar.
Let’s assemble them in a new screen.

Core Components, Part 2

203

Comments screen
Create a new file Comments.js within the screens directory.
Within our new screen, we’ll want to render first the NavigationBar, then the CommentInput, and
finally the CommentList. We want this screen to take 4 props:
•
•
•
•

comments - The array of comments to display.
onClose - A function prop to call when the user presses the close button.
onSubmitComment - A function prop to call when the user adds a new comment.
style - The style to apply to the top-level View of this screen (just like we did with Feed)

Add the following to Comments.js:
image-feed/screens/Comments.js
1
2
3

import { SafeAreaView, ViewPropTypes } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';

4
5
6
7

import CommentInput from '../components/CommentInput';
import CommentList from '../components/CommentList';
import NavigationBar from '../components/NavigationBar';

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

export default function Comments({
style,
comments,
onClose,
onSubmitComment,
}) {
return (





);
}

27
28

Comments.propTypes = {

Core Components, Part 2
29
30
31
32
33

204

style: ViewPropTypes.style,
comments: PropTypes.arrayOf(PropTypes.string).isRequired,
onClose: PropTypes.func.isRequired,
onSubmitComment: PropTypes.func.isRequired,
};

34
35
36
37

Comments.defaultProps = {
style: null,
};

The code for our screen is fairly simple, since we already built the different parts of the UI as
individual components.

Putting it all together
Now we need to allow navigation from the Feed screen we made earlier with this new Comments
screen.
We want the Comments screen to slide up and cover the entire screen, so we’ll use the built-in Modal
component.

Modal
The Modal component lets us transition to an entirely different screen. This is most useful for simple
apps, since for complex apps you’ll likely be using a navigation library which will come with its
own way of doing modals.
Common props include:
• animationType - This controls how the modal animates in and out. One of 'none', 'slide', or
'fade' (defaults to 'none').
• onRequestClose - A function called when the user taps the Android back button.
• onShow - A function called after the modal is fully visible.
• transparent - A bool determining whether the background of the modal is transparent.
• visible - A bool determining whether the modal is visible or not.
The visible prop is the most important, since this lets us show and hide the Modal.

Core Components, Part 2

205

Adding Modal to App
We’ll maintain the state of the Modal in the state of our App component. We’ll use the visible prop of
the Modal component to show and hide it, so we’ll want to store a boolean showModal in state. We’ll
also want to store the id of the image we’re viewing comments for, so we’ll store selectedItemId
in state too. And we’ll want to store the actual text for the comments we type in, so let’s create an
object that maps from an image id to an array of comment strings. Let’s update the state in App.js
to look like this:
image-feed/App.js

state = {
commentsForItem: {},
showModal: false,
selectedItemId: null,
};

Next, we’ll make two function properties on our App component, for updating state in order to open
and close the Modal.
image-feed/App.js

openCommentScreen = id => {
this.setState({
showModal: true,
selectedItemId: id,
});
};
closeCommentScreen = () => {
this.setState({
showModal: false,
selectedItemId: null,
});
};

Notice that our openCommentScreen function takes the id of the image we want to display comments
for. We’ll need to call this function from within the CardList in order pass that id. Then we’ll
propagate the value through Feed and up to App. Save App.js and let’s head over to CardList.js to
make this possible.

Updating CardList with comments
We want the “Comments” link on each card to open the Modal we just created:

Core Components, Part 2

206

To do this, let’s tweak our CardList component, adding an onPressComments prop (which we can use
to call openCommentScreen) and a commentsForItem prop (which we can use to display the number
of comments per image).
image-feed/components/CardList.js

// ...
export default class CardList extends React.Component {
static propTypes = {
items: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
author: PropTypes.string.isRequired,
}),
).isRequired,
commentsForItem: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string))
.isRequired,
onPressComments: PropTypes.func.isRequired,
};
// ...
}
// ...

Let’s call onPressComments from within renderItem, passing the id of the item so that we know
which image to display comments for.
image-feed/components/CardList.js

renderItem = ({ item: { id, author } }) => {
const { commentsForItem, onPressComments } = this.props;
const comments = commentsForItem[id];
return (
 onPressComments(id)}
/>

Core Components, Part 2

207

);
};

There’s one small problem with our CardList so far: the count of comments we use for the linkText
won’t immediately update when we add new comments. This is due to how the FlatList decides
whether or not to re-render items; the FlatList will only re-render an item when the data prop
changes or when scrolling. In this case, we pass the items prop of CardList into the data prop of
FlatList, but our commentsForItem prop doesn’t cause the items array to change, so the FlatList
won’t update when new comments are added. We can use the prop extraData of FlatList to inform
the FlatList that it should monitor another source of input data for changes.
Let’s update the render method within CardList, passing commentsForItem as the extraData prop
of FlatList.
image-feed/components/CardList.js

// ...
const { items, commentsForItem } = this.props;
return (

);
// ...

Save CardList.js. This will put your app in an error state, since CardList isn’t currently being
passed a commentsForItem prop.

Core Components, Part 2

208

Let’s fix that!

Updating Feed with comments
Open Feed.js. We need to accept commentsForItem and onPressComments here too, and pass them
into CardList.
Update the propTypes:
image-feed/screens/Feed.js

static propTypes = {
style: ViewPropTypes.style,
commentsForItem: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string))
.isRequired,
onPressComments: PropTypes.func.isRequired,
};

And update the render method:

Core Components, Part 2

209

image-feed/screens/Feed.js

render() {
const { commentsForItem, onPressComments, style } = this.props;
// ..
return (



);
}
// ...

Save Feed.js. You should still see the same error message, since we’re still not passing a value for
commentsForItem into Feed.

Updating App with comments
Let’s head back to App.js to connect these new props we’ve just added to Feed and to render the
screen.
Import the Modal component and our Comments component:
image-feed/App.js

import { Modal, Platform, StyleSheet, View } from 'react-native';
// ...
import Comments from './screens/Comments';

Then update the render method to render Comments and update Feed with new props:

Core Components, Part 2

image-feed/App.js

// ...
export default class App extends React.Component {
// ...
render() {
const { commentsForItem, showModal, selectedItemId } = this.state;
return (






);
}
}
// ...

We’ll also add one new style for the comments screen:

210

Core Components, Part 2

211

image-feed/App.js

// ...
const styles = StyleSheet.create({
// ...
comments: {
flex: 1,
marginTop:
Platform.OS === 'ios' && platformVersion < 11
? Constants.statusBarHeight
: 0,
},
});

Like before, we need to handle iOS versions below 11 separately by adding a top margin. Modals
naturally sit below the status bar on Android, so we only need a top margin on iOS in this case.
After saving App.js, you’ll be able to open and close the Comments screen! Tap any "Comments" link
to open it, and tap the "Close" button in the NavigationBar to close it.
There should still be a warning about a missing onSubmitComment prop. Let’s add that next.

Adding new comments
Now that we can access our new Comments screen, we’ll want to be able to type new comments.
Let’s create a function property onSubmitComment on our App component for saving a new comment
into the commentsForItem object in our state. Since our commentsForItem object should be immutable
(it’s part of state), we’ll create a new object and copy over the existing keys and values using the
... object spread syntax. For our selectedItemId, we’ll either update the comments array within
commentsForItem, copying over existing comments with the ... array spread syntax, or we’ll create
a new array if this is the first comment.
image-feed/App.js

// ...
onSubmitComment = (text) => {
const { selectedItemId, commentsForItem } = this.state;
const comments = commentsForItem[selectedItemId] || [];
const updated = {
...commentsForItem,
[selectedItemId]: [...comments, text],

Core Components, Part 2

212

};
this.setState({ commentsForItem: updated });
};
// ...

Since we’re creating a new commentsForItem object, when we end up passing it into Feed, the Feed
will pass it to the FlatList as extraData, triggering a re-render (updating the "0 Comments" text).
Computed property names
When defining object literals, we can dynamically compute property names by putting array
brackets around the property name. For example:
const name = 'foo';
const obj = { [name]: 'bar' };
console.log(obj.foo); // => 'bar'

This is roughly equivalent to:
const name = 'foo';
const obj = {};
obj[name] = 'bar';

Computed property names are convenient in cases like the above example, where we want the object
literal to have property names based on dynamic values.

The last step for typing new comments is to pass the onSubmitComment function to the Comments
component when rendering:
image-feed/App.js

// ...
return (



Core Components, Part 2

213





);
// ...

Save App.js, then go ahead and play around with the app for a bit! You should be able to tap the “0
Comments” text at the top right of each image to open up the Modal containing the Comments screen.
You should be able to type new comments and see them appear in the list of comments. When you
close the Modal, you should see the number of comments has increased for the image you chose.

Bonus: Persisting comments to device storage
You may have noticed that any comments you added will disappear if you reload the app. This is
because we don’t save them anywhere.
As an optional final step, we can persist the comments we write to the device via the AsyncStorage
API. AsyncStorage is a simple key-value store provided by React Native for storing small quantities of string data (which we usually serialize as JSON). Like the name implies, saving and
reading from this store both happen asynchronously. We can call AsyncStorage.getItem(key) and
AsyncStorage.setItem(key, value) to store and retrieve a string value using a string key.
In App.js, we’ll first need to import AsyncStorage:
image-feed/App.js

import { AsyncStorage, Modal, Platform, StyleSheet, View } from 'react-native';

Then we’ll define an arbitrary key for persisting our comments object as JSON:

Core Components, Part 2

214

image-feed/App.js

const ASYNC_STORAGE_COMMENTS_KEY = 'ASYNC_STORAGE_COMMENTS_KEY';

Then we’ll update our componentDidMount and onSubmitComment to save and read from AsyncStorage,
respectively. Since we can only store string values using AsyncStorage, if we want to store a complex
object, we’ll have to serialize it to JSON first. To do this, we can call JSON.stringify before storing
values and JSON.parse after retreiving them.
We’ll load all comments into state when our App mounts:
image-feed/App.js

async componentDidMount() {
try {
const commentsForItem = await AsyncStorage.getItem(
ASYNC_STORAGE_COMMENTS_KEY,
);
this.setState({
commentsForItem: commentsForItem ? JSON.parse(commentsForItem) : {},
});
} catch (e) {
console.log('Failed to load comments');
}
}

Then we’ll update the stored comments anytime we add a new comment by modifying onSubmitComment:
// ...
onSubmitComment = text => {
const { selectedItemId, commentsForItem } = this.state;
const comments = commentsForItem[selectedItemId] || [];
const updated = {
...commentsForItem,
[selectedItemId]: [...comments, text],
};
this.setState({ commentsForItem: updated });
try {
AsyncStorage.setItem(ASYNC_STORAGE_COMMENTS_KEY, JSON.stringify(updated));

Core Components, Part 2

215

} catch (e) {
console.log('Failed to save comment', text, 'for', selectedItemId);
}
};
// ...

Note that getItem and setItem can both fail (e.g. when disk I/O fails), so we need to wrap any async
calls in try/catch.
That’s all we had to do to persist comments to disk! Now when you write comments and reload the
app, they’ll still be there. Give it a shot!

Wrapping up
Many of the built-in components we’ve covered in this chapter are highly generic and resusable:
View, Text, Image, ScrollView, and FlatList. The bulk of the UI in most apps will be written with
a combination of these components.
We covered a few other components which are for more specialized use cases, like ActivityIndicator,
TextInput, and Modal. There are many more components like this which we didn’t cover.
You don’t need to memorize every built-in React Native component – in fact, there are some
components you’ll probably never need. The important thing is: you now have a strong foundation
in how React Native works, so you’ll be able to figure out how to use any built-in component just
by reading the docs.
UI components are a huge part of what React Native has to offer. However, most apps need more than
just UI components. There’s another big part of React Native which we touched on in this chapter:
imperative APIs. These are APIs (like AsyncStorage) which we can call from the component lifecycle
to fetch data, access the camera roll, query our geolocation, etc. In the next chapter, we’ll explore
some of the most common React Native APIs.

Core APIs, Part 1
So far we’ve primarily used React components to interact with the underlying native APIs – we’ve
used components like View, Text, and Image to create native UI elements on the screen.
React provides a simple, consistent interface for APIs which create visual components. Some APIs
don’t create UI components though: for example, accessing the Camera Roll, or querying the current
network connectivity of the device.
React Native also comes with APIs for interacting with these non-visual native APIs. In contrast
with components, these APIs are generally imperative functions: we must call them explicitly at
the right time, rather than returning something from a component’s render function and letting
React call them later. React Native simply provides us a JavaScript wrapper, often cross-platform,
for controlling the underlying native APIs.

Building a messaging app
In this chapter, we’ll build the start of a messaging app (similar to iMessage) that gives us a tour of
some of the most common core APIs. Our app will let us send text, send photos from the camera
roll, and share our location. It will let us know when we are disconnected from the network. It will
handle keyboard interactions and the back button on Android.
To try the completed app:
• On Android, you can scan the following QR code from within the Expo app:

• On iOS, you can navigate to the messaging/ directory within our sample code folder and build
the app. You can either preview it using the iOS simulator or send the link of the project URL
to your device as we mentioned in the first chapter.

Core APIs, Part 1

We can send text messages, images, and maps:

We can choose images from our device camera roll:

217

Core APIs, Part 1

And we can view images fullscreen:

218

Core APIs, Part 1

219

We’ll use the following APIs:
•
•
•
•
•
•
•

Alert - Displays modal dialog windows for simple user input
BackHandler - Controls the back button on Android
CameraRoll - Returns images and videos stored on the device
Dimensions - Returns the dimensions of the screen
Geolocation - Returns the location of the device, and emits events when the location changes
Keyboard - Emits events when the keyboard appears or disappears
NetInfo - Returns network connectivity information, and emits events when the connectivity

changes
• PixelRatio - Translates from density-independent pixels to density-dependent pixels (more
detail on the later)
• StatusBar - Controls the visibility and color of the status bar
We’ll just be focusing on the UI, so we won’t actually send messages, but we could connect the UI
we build to a backend if we wanted to use it in a production app.

Initializing the project
Just as we did in the previous chapters, let’s create a new app with the following command:

Core APIs, Part 1

220

$ create-react-native-app messaging --scripts-version 1.14.0

Once this finishes, navigate into the messaging directory.
In this chapter we’ll create the utils directory ourselves, so there’s no need to copy over the sample
code.

The app
Let’s start by setting up the skeleton of the app. We’ll do this in App.js. After that, we’ll build out
the different parts of the screen, one component at a time. We’ll tackle keyboard handling last, since
that’s the most difficult and intricate.
We’ll follow the same general process as in the previous chapters: we’ll start by breaking down the
screen into components, building a hardcoded version, adding state, and so on.

The app’s skeleton
If we look at the app from top to bottom, these are the main sections of the UI:

Core APIs, Part 1

221

• Status - The device generally renders a status bar, the horizontal strip at the top of the
screen that shows time, battery life, etc – but in this case, we’ll augment it to show network
connectivity more prominently. We’ll create our own component, Status, which renders
beneath the device’s status bar.
• MessageList - This is where we’ll render text messages, images, and maps.
• Toolbar - This is where the user can switch between sending text, images, or location, and
where the input field for typing messages lives.
• Input Method Editor (IME) - This is where we can render a custom input method, i.e. sending
images. We’ll build an image picker component, ImageGrid, and use it here. Note that the
keyboard is rendered natively by the operating system, so we will trigger the keyboard to
appear and disappear at the right times, but we won’t render it ourselves.
In this chapter we’ll be building top-down. We’ll start by representing the message list, the toolbar,
and the IME with a placeholder View. By starting with a rough layout, we can then create components
for each section, putting each one in its respective View. Each section will be made up of a few
different components.

Core APIs, Part 1

Open App.js and add the following skeleton:
messaging/App.js

import { StyleSheet, View } from 'react-native';
import React from 'react';
export default class App extends React.Component {
renderMessageList() {
return (

);
}
renderInputMethodEditor() {
return (

);
}
renderToolbar() {
return (

);
}
render() {
return (

{this.renderMessageList()}
{this.renderToolbar()}
{this.renderInputMethodEditor()}

);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
content: {
flex: 1,

222

Core APIs, Part 1

223

backgroundColor: 'white',
},
inputMethodEditor: {
flex: 1,
backgroundColor: 'white',
},
toolbar: {
borderTopWidth: 1,
borderTopColor: 'rgba(0,0,0,0.04)',
backgroundColor: 'white',
},
});

When you save App.js, the app should reload on your device and you’ll see the following:

Awesome, a blank screen with a small gray line through the middle! Now we can start building out
the different sections of the screen. The App component will orchestrate how data is populated, and
when to hide or show the various input methods – but first, we need to start creating the different
components in the UI.
Now’s a good time to create a new directory, components, within our main messaging directory.
We’ll put the UI components we build in the components directory.

Core APIs, Part 1

224

Network connectivity indicator
Since we’re building a messaging app, network connectivity is relevant at all times. Let’s let the user
know when they’ve lost connectivity by turning the status bar red and displaying a short message.

StatusBar
Many apps display the default status bar, but sometimes we want to customize the style, e.g. turning
the background red.
The status bar works a little differently on iOS and Android. On iOS the status bar background is
always transparent, so we can render content behind the status bar text. On Android, we can set
the status bar background to transparent, or to a specific solid color. If we use a transparent status
bar, we can render content behind it just like on iOS – unlike on iOS, by default the status bar text
is white and there’s typically a semi-transparent black background. If we choose a solid color status
bar, our app’s content renders below the status bar, and the height of our UI will be a little smaller.
In our app, we’ll use a solid color status bar, since this will let us customize the color.
To use a solid color status bar, we need to open up app.json and add the following to the expo object
(although you can skip this if you’re not using an Android):

Core APIs, Part 1

225

messaging/app.json

"expo": {
// ...
"androidStatusBar": {
"barStyle": "dark-content",
"backgroundColor": "#FFFFFF"
}
}

Let’s restart the packager with npm run start to make sure this change takes effect.
If we had used react-native init instead of create-react-native-app, we wouldn’t
need to do this. Expo handles the status bar specially. You can check out the guide on
configuring the status bar61 for more detail.
On both platforms, we can set the status bar text color by using the built-in StatusBar component
and passing a barStyle of either light-content (white text) or dark-content (black text).
There are two different ways we can use StatusBar: imperatively and as a component. In this
example we’ll use the component approach.
Create a new file Status.js in the components directory now.

Status styles
Let’s first start with the background styles. We need to create a View that sits behind the text of the
status bar – on iOS, rendering the background color of the status bar is our responsibility, since the
operating system only renders the status bar text.
We’ll have two visual states: one where the user is connected to the network, and one where the
user is disconnected. We’ll set the color for each state in render, so let’s start with the base style for
the status bar:

61 https://docs.expo.io/versions/latest/guides/configuring-statusbar.html

Core APIs, Part 1

226

messaging/components/Status.js

import { Constants } from 'expo';
import { StyleSheet } from 'react-native';
// ...
const statusHeight =
(Platform.OS === 'ios' ? Constants.statusBarHeight : 0);
const styles = StyleSheet.create({
status: {
zIndex: 1,
height: statusHeight,
},
// ...
});

The base style status will give the View its height. The View will have the same height regardless of
whether this component is in the connected or disconnected state. We use a zIndex of 1 to indicate
that this View should be drawn on top of other content – this will be relevant later, since we’re going
to render a ScrollView beneath it.
Depending on the component’s state, we’ll then pass a style object containing a background color
(in addition to passing the status style).
We’ll store the network connectivity status in component state as state.info. Network connectivity status can have several different states, but let’s assume for now that if the state.info is "none"
then we’re disconnected, and anything else means we’re connected.
Let’s try rendering this background View.
messaging/components/Status.js

import { Constants } from 'expo';
import { NetInfo, Platform, StatusBar, StyleSheet, Text, View } from 'react-native';
import React from 'react';
export default class Status extends React.Component {
state = {
info: null,
};
// ...
render() {

Core APIs, Part 1

227

const { info } = this.state;
const isConnected = info !== 'none';
const backgroundColor = isConnected ? 'white' : 'red';
if (Platform.OS === 'ios') {
return ;
}
return null; // Temporary!
}
}
// ...

Notice how we use an array for the View to apply two styles: the status style, and then a style object
containing a different background color depending on whether we’re connected to the network or
not.
Let’s save Status.js and import it from App.js so we can see what we have so far.
We can now go ahead and render our new Status component from App:
messaging/App.js

// ...
import Status from './components/Status';
export default class App extends React.Component {
// ...
render() {
return (


{this.renderMessageList()}
{this.renderToolbar()}
{this.renderInputMethodEditor()}

);
}
// ...

Core APIs, Part 1

228

}
// ...

We shouldn’t see anything yet… but to verify that everything is working, you can temporarily set
info: 'none' in the state of Status. This will show a red background behind the status bar text.
Doing this, we should see:

Using StatusBar
The black text on the red background doesn’t look very good. This is where the StatusBar component
comes in. Let’s import it from react-native and render it within our View.

Core APIs, Part 1

229

messaging/components/Status.js

import { Constants } from 'expo';
import { StatusBar, StyleSheet, View } from 'react-native';
import React from 'react';
export default class Status extends React.Component {
state = {
info: null,
};
// ...
render() {
const { info } = this.state;
const isConnected = info !== 'none';
const backgroundColor = isConnected ? 'white' : 'red';
const statusBar = (

);
if (Platform.OS === 'ios') {
return {statusBar};
}
return null; // Temporary!
}
}

Here we set barStyle to dark-content if we’re connected (black text on our white background) and
light-content if we’re disconnected (white text on our red background). We set backgroundColor
to set the correct background color on Android. We also set animated to false – since we’re not
animating the background color on iOS, animating the text color won’t look very good.
Note that the StatusBar component doesn’t actually render the status bar text. We use this
component to configure the status bar. We can render the StatusBar component anywhere in the
component hierarchy of our app to configure it, since the status bar is configured globally.

Core APIs, Part 1

230

We can even render StatusBar in multiple different components, e.g. we could render it from App.js
in addition to Status.js. If we do this, the props we set as configuration are merged in the order the
components mount. In practice it can be a bit hard to follow the mount order, so it may be easier to
use the imperative API if you find yourself with many StatusBar components (more on this later).

Message bubble
Since the red status bar alone doesn’t indicate anything about network connectivity, let’s also add a
short message in a floating bubble at the top of the screen.

If we’re not connected to the network, we’ll render a few more components. On Android, since we
don’t need to render the background behind the status bar, we can return just the message bubble
components.

Core APIs, Part 1

231

messaging/components/Status.js
import { Constants } from 'expo';

import { NetInfo, StatusBar, StyleSheet, Text, View } from 'react-native';
import React from 'react';
export default class Status extends React.Component {
// ...
render() {
const { info } = this.state;
const isConnected = info !== 'none';
const backgroundColor = isConnected ? 'white' : 'red';
const statusBar = (

);
const messageContainer = (

{statusBar}
{!isConnected && (

No network connection

)}

);
if (Platform.OS === 'ios') {
return {messageContainer};
}
return messageContainer;
}
}
const styles = StyleSheet.create({

Core APIs, Part 1

232

// ...
messageContainer: {
zIndex: 1,
position: 'absolute',
top: statusHeight + 20,
right: 0,
left: 0,
height: 80,
alignItems: 'center',
},
bubble: {
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 20,
backgroundColor: 'red',
},
text: {
color: 'white',
},
});

A future update to this chapter will fix iPhone X style issues.

Here we use absolute position to precisely position the message bubble on top of the rest of the content we’ll render, without pushing our other content out of the way. We use pointerEvent={'none'}
so that this component doesn’t prevent us from tapping the ScrollView we’ll render it. The
pointerEvents prop allows us to control whether an component can respond to touch interactions,
or whether they pass through to the components behind it.
Save Status.js and you should see the following.

Core APIs, Part 1

233

Our connectivity indicator UI is looking good! Now it’s time to hook it up to the device’s real network
connectivity state.

NetInfo
We have info in state, and we have logic to switch between showing a connected and disconnected
UI in our render method. Now we need to update this state whenever network connectivity changes.
We can do this using the NetInfo APIs.
The NetInfo APIs are a good example of React Native core APIs: these provide a uniform interface
to the lower level native APIs on iOS and Android. React Native is essentially providing JavaScript
bindings and smoothing out platform differences for us.
We can call NetInfo.getConnectionInfo() to get the network connectivity status. NetInfo.getConnectionInfo()
returns a promise which resolves to a string. If the device is connected, the string value will be 'wifi'
or 'cellular' If the device isn’t connected, the promise will still resolve, but with the value 'none'.
If we wanted to update our UI when the network connection changes, we could continuously poll
NetInfo.getConnectionInfo() to get the network status – but this would be inefficient. Instead, we
can add an event listener to NetInfo. NetInfo provides the method addEventListener, which we
can call with a callback function, which it will invoke each time the network status changes.
Here’s an example of using NetInfo.addEventListener:

Core APIs, Part 1

234

const subscription = NetInfo.addEventListener('connectionChange', (status) => {
console.log('Network status changed', status)
});

This example would log a new status each time the network connectivity changes. We can call
subscription.remove() when we want to stop listening for changes – most of the time, we’ll do
this when our component unmounts.
For our app, we’ll use both NetInfo.getConnectionInfo and NetInfo.addEventListener. First we’ll
call NetInfo.getConnectionInfo when the Status component mounts to get the initial network
connectivity. Then we’ll use NetInfo.addEventListener to update our UI when a change occurs.
Let’s add the following lines to our Status component in Status.js:
1

// ...

2
3
4
5

async componentWillMount() {
this.subscription = NetInfo.addEventListener('connectionChange', this.handleChange\
);

6

const info = await NetInfo.getConnectionInfo();

7
8

this.setState({ info });

9
10

}

11
12
13
14

componentWillUnmount() {
this.subscription.remove();
}

15
16
17
18

handleChange = (info) => {
this.setState({ info });
};

19
20

// ...

Now we receive both the initial status and handle connectivity changes.
Note that we declared componentWillMount as an async method, so that we can use await when
calling NetInfo.getConnectionInfo(). Most of the React lifecycle methods can be declared with
async, since React doesn’t use the return value from these.
To test changes in network connectivity without setting the device to airplane mode,
we can add: setTimeout(() => this.handleChange('none'), 3000); to the end of
componentWillMount. This way we can observe the transition from our initial state
(probably 'wifi') to the disconnected state.

Core APIs, Part 1

235

For reference, if we wanted to use the imperative approach to changing the status bar style, we
would write our handleChange as:
handleChange = (info) => {
this.setState({ info });
StatusBar.setBarStyle(info === 'none' ? 'light-content' : 'dark-content');
};

We would then remove the  component from our render function. The
StatusBar component is a little unusual because it doesn’t actually render anything. Under the
hood, the StatusBar component just calls StatusBar.setBarStyle at the appropriate times. Calling
the imperative APIs directly can be simpler than figuring out how and where to render StatusBar
components in a complex app.

Wrapping up StatusBar and NetInfo
We’re finished with the status bar and network connectivity indicator! We’ve just written a crossplatform UI that works on both iOS and Android, with only a little bit of platform-specific code.
For future improvements, we could consider animating the message bubble as it appears and
disappears, and animating the status bar as it changes colors. We’ll cover this kind of animation
in more depth in a later chapter. For now, let’s move on to the message list.

The message list
Let’s create the message list. The message list will display a vertically scrolling list of text messages,
image messages, and location messages. We should be able to tap the messages to potentially trigger
other actions (e.g. view the image fullscreen).

Core APIs, Part 1

236

We’ll use the FlatList component we learned about in the previous chapter to handle rendering the
list. In order to do that, we should first decide how we’ll store our message objects.

MessageUtils
Let’s first write a few utility functions for creating message objects so that we keep this logic separate
from our rendering logic.
Create a new directory called utils in the messaging directory. Within utils, create a new file
called MessageUtils.js.
Within MessageUtils.js, let’s first define the shape of each message using PropTypes.shape. All of
the messages we render will have a type and an id, and then some messages will have either a text,
uri, or coordinate value, depending on the type.
Add the following to MessageUtils.js:

Core APIs, Part 1

237

messaging/utils/MessageUtils.js

import PropTypes from 'prop-types';
export const MessageShape = PropTypes.shape({
id: PropTypes.number.isRequired,
type: PropTypes.oneOf(['text', 'image', 'location']),
text: PropTypes.string,
uri: PropTypes.string,
coordinate: PropTypes.shape({
latitude: PropTypes.number.isRequired,
longitude: PropTypes.number.isRequired,
}),
});

By using this shape in the propTypes of a component, React will automatically warn us if we
accidentally pass invalid message data. Declaring our data models this way is also great for
documentation purposes: if another developer reads our component, they’ll know exactly what the
input data should look like, without having to sprinkle console.log throughout the app and actually
run it.
Declaring our data models this way is optional. For a model that will likely be used in many
places throughout the app, it’s probably worthwhile to spend the extra effort. It isn’t as
valuable for a model used within a single component, or a model that you’re still iterating
on during development. If you decide to use a strongly-typed variant of JavaScript, i.e. Flow
or TypeScript, you’ll likely declare your types elsewhere and won’t need to also declare
PropTypes.

Next, let’s write a few utility functions for creating the different kinds of messages:
messaging/utils/MessageUtils.js

let messageId = 0;
function getNextId() {
messageId += 1;
return messageId;
}
export function createTextMessage(text) {
return {
type: 'text',
id: getNextId(),
text,

Core APIs, Part 1

238

};
}
export function createImageMessage(uri) {
return {
type: 'image',
id: getNextId(),
uri,
};
}
export function createLocationMessage(coordinate) {
return {
type: 'location',
id: getNextId(),
coordinate,
};
}

We created a utility getNextId() for getting a unique message id. It’s important that we ensure
uniqueness for each id, since we’ll be using the id as the key when rendering these messages in a
list.
We would likely want to use a more sophisticated id, such as a UUID, if we were actually
connecting with a backend. Incrementing a number works for our purposes, but once
messages are persisted or coming from multiple devices, there would be id collisions.
By exporting createTextMessage, createImageMessage, and createLocationMessage, we can now
easily create new messages of each type from elsewhere in our app. We’ll use these messages to
populate the FlatList.

MessageList
Our MessageList component will render an array of the message objects we defined in MessageUtils.
We can determine how to render each message based on its type.
Let’s start by determining the propTypes for this component. Here’s a good opportunity to use the
MessageShape we just defined. We’ll also want to notify the parent component whenever a message
in the list is pressed. We can do this using an onPressMessage function prop.
Create a new file, MessageList.js, in our components directory. Add the following to it:

Core APIs, Part 1

239

messaging/components/MessageList.js

import React from 'react';
import PropTypes from 'prop-types';
import { MessageShape } from '../utils/MessageUtils';
export default class MessageList extends React.Component {
static propTypes = {
messages: PropTypes.arrayOf(MessageShape).isRequired,
onPressMessage: PropTypes.func,
};
static defaultProps = {
onPressMessage: () => {},
};
// ...
}

By using our MessageShape, React will warn us if we’re passed malformed data.
Now let’s render our messages into a FlatList. Just as in the previous chapter, We need to use a
keyExtractor to tell the FlatList how to find the unique id of our message objects.
Let’s update MessageList.js to render a FlatList:
messaging/components/MessageList.js

import { FlatList, StyleSheet } from 'react-native';
// ...
const keyExtractor = item => item.id.toString();
export default class MessageList extends React.Component {
// ...
renderMessageItem = ({ item }) => {
// ...
};
render() {
const { messages } = this.props;

Core APIs, Part 1

240

return (

);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
overflow: 'visible', // Prevents clipping on resize!
},
});

We looked at the data, renderItem, and keyExtractor props in the previous chapter. There are a few
new props here that are worth looking at in more detail.
inverted

In a messaging app, we typically want new messages to appear at the bottom of the list. To
accomplish this, we’ve added the inverted prop to our FlatList.
This “new-messages-at-the-bottom” behavior is difficult to achieve without using inverted.
If we didn’t use inverted, every time a new message is added, we would have to scroll to the
bottom of the list by adding a ref to the list and calling the scrollToEnd method. While it
may sound relatively simple, it quickly gets complicated when we start adding asynchronous
animations, e.g. in response to the keyboard appearing. Since ScrollView doesn’t support
inverted, we almost always want to use a FlatList for this.
Behind-the-scenes, our FlatList is vertically inverted using a transform style, and then
each row within the list is also vertically inverted. Since rows are doubly inverted, they
appear right-side-up. Pretty clever!

keyboardShouldPersistTaps

We use the keyboardShouldPersistTaps prop to configure what happens when we tap the FlatList.
This prop has three possible options:

Core APIs, Part 1

241

• never - Tapping the list will dismiss the keyboard and blur any focused elements. This is the
default behavior.
• always - Tapping the list will have no effect on the keyboard or focus.
• handled - Tapping the list will dismiss the keyboard, unless the tap is handled by a child
element first (e.g. tapping a message within the list). We want handled, so that we enable
tapping messages without dismissing the keyboard.
We add overflow: 'visible' to the style of the FlatList to prevent content from getting clipped
during animations. When an animation causes the list to resize to a smaller size, the content within it
will be clipped to the smaller size instantly, while the list itself resizes gradually. If we don’t include
this line, some content will get clipped at the start of the animation that should actually be clipped
at the end. It’ll be easier to understand why this is necessary after we’re a bit further along. You can
comment out this property and observe the difference in behavior as the keyboard appears.

Rendering messages
We’ve successfully set up a scrolling list, so now we can populate it with messages. As a reminder,
this is what we’re aiming to build:

Let’s start with the styles. Conceptually, each message is a row in the list, so let’s call our top-level
message style messageRow and give it flexDirection: 'row'. We want to align messages to the right

Core APIs, Part 1

242

using justifyContent: 'flex-end', but leave a little space on the left with marginLeft: 60 in case
our message gets long. Text messages should appear in blue bubbles: this is what messageBubble is
for.
messaging/components/MessageList.js

const styles = StyleSheet.create({
container: {
flex: 1,
overflow: 'visible', // Prevents clipping on resize!
},
messageRow: {
flexDirection: 'row',
justifyContent: 'flex-end',
marginBottom: 4,
marginRight: 10,
marginLeft: 60,
},
messageBubble: {
paddingVertical: 5,
paddingHorizontal: 10,
backgroundColor: 'rgb(16,135,255)',
borderRadius: 20,
},
text: {
fontSize: 18,
color: 'white',
},
image: {
width: 150,
height: 150,
borderRadius: 10,
},
map: {
width: 250,
height: 250,
borderRadius: 10,
},
});

Feel free to experiment with other styles. There are a lot of ways to customize a messaging
app to give it a unique look.

Core APIs, Part 1

243

Let’s move on to renderMessageItem and begin using the styles we just created. We can start by
updating our imports to include all of the components we’ll render from MessageList:
import { FlatList, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-nat\
ive';
import { MapView } from 'expo';

Here we’ll use MapView for the first time. You’ll notice we import this from Expo, rather than from
React Native. MapView comes from the 3rd party module react-native-maps, which Expo includes
by default. If we had created our app via react-native-cli rather than create-react-native-app,
we would have to remember to install and link react-native-maps.
If you’re running an Android emulator, you’ll need the Google Play Services installed to
actually see a MapView (otherwise you’ll see a placeholder label). You can create an emulator
from Android Studio with this, but it can be difficult to connect it to Expo. If you don’t know
how to do this already, we recommend testing on a real Android device if possible.

React Native used to include a built-in MapView, but this has been removed in favor of
react-native-maps. The react-native-maps module quickly became the de-facto standard
for using maps in React Native, obsoleting the original built-in version.

Then let’s add the following:
messaging/components/MessageList.js

// ...
renderMessageItem = ({ item }) => {
const { onPressMessage } = this.props;
return (

 onPressMessage(item)}>
{this.renderMessageBody(item)}


);
};
renderMessageBody = ({ type, text, uri, coordinate }) => {
// ...
}

Core APIs, Part 1

244

// ...

Just as in the previous chapters, we use the id of the item as the key of the top-level element we
return, so React can keep track of existing items. Without this, React would have to re-render all
items when we add more, since it wouldn’t know which items are old and which are new.
We want each message to be tappable, so we wrap the message body in a TouchableOpacity, and
call onPressItem with the item object when tapped.
Finally, we call this.renderMessageBody with the item (our message), where we’ll render the body
of each message. We’ll switch on the type of the message to decide what to render.
messaging/components/MessageList.js

export default class MessageList extends React.Component {
// ...
renderMessageBody = ({ type, text, uri, coordinate }) => {
switch (type) {
case 'text':
return (

{text}

);
case 'image':
return ;
case 'location':
return (



);
default:
return null;
}
};

Core APIs, Part 1

245

// ...
}

Each type of message, text, image, and location has a different kind of UI. Most of the components
we used for text and image should look pretty familiar.
For location messages, we use a MapView. The MapView API is fairly advanced, allowing custom
drawing and animations on top of maps. We’ll use the initialRegion to supply a bounding box
to display on the map, and we’ll create a MapView.Marker to drop a pin at the coordinate in the
message. You can read more about the MapView API in the official docs for react-native-maps62 .
We now have a scrollable, tappable list of messages that supports different kinds of content. Let’s
test it out.

Adding MessageList to App
To test our new MessageList component, we can render it from within the renderMessageList
method of App.js.
Heading back to App.js now, we’ll need to import our new MessageList component and our utility
functions for creating messages:
messaging/App.js

// ...
import MessageList from './components/MessageList';
import { createImageMessage, createLocationMessage, createTextMessage } from './util\
s/MessageUtils';
//...

We can use these utility functions to create a few sample messages in the initial state of our app:

62 https://github.com/airbnb/react-native-maps

Core APIs, Part 1

246

messaging/App.js

// ...
state = {
messages: [
createImageMessage('https://unsplash.it/300/300'),
createTextMessage('World'),
createTextMessage('Hello'),
createLocationMessage({
latitude: 37.78825,
longitude: -122.4324,
}),
],
};
handlePressMessage = () => {}
renderMessageList() {
const { messages } = this.state;
return (



);
}
// ...

We’ve now hooked up the hardcoded message data with our MessageList component. We also
added a placeholder handlePressMessage for handling tapping messages. When you save App.js, if
everything is working correctly, here’s what you should see:

Core APIs, Part 1

247

We’re successfully rendering our message list! We can display messages of different types and our
new messages appear at the bottom, just like we’d expect from a messaging app.
At this point, we have the prop onPressMessages set to the empty function handlePressMessage.
Let’s hook up a few different actions to onPressMessages.

Alert
The first action we’ll add is to text messages. We’ll add a “delete” feature: when the user taps a
text message, we’ll give the user the option to delete that message. We’ll present a dialog with two
choices: delete and cancel.
We can use the Alert.alert API to present the user with a native dialog window for making this
choice.

Core APIs, Part 1

248

Alert dialogs are commonly used for asking simple “yes or no” questions. The text and quantity of
buttons are configurable.
Alert dialogs can also be used for debugging. Sometimes it can be easier to pop open an alert
dialog than tracking down a console.log message.

The full method signature is Alert.alert(title, message?, buttons?, options?, type?):
• title - A string, shown in a large bold font, at the top of the dialog
• message - A string, typically longer, shown in a normal weight font below the title
• buttons - An array of objects containing text (a string), onPress (a callback function), and
optionally a style on iOS (styles can be one of default, cancel, or destructive).
• options - An object for controlling the dialog dismissal behavior on Android. Tapping outside
the dialog will normally exit the dialog. This can be prevented by setting { cancelable: false
} or handled specially with { onDismiss: () => {} }.
• type - Allows text entry on iOS using one of the following options: default, plain-text,
secure-text, or login-password.
Let’s trigger an alert from App.js. First we’ll need to import Alert:

Core APIs, Part 1

249

messaging/App.js

import {
Alert,
// ...
} from 'react-native';

Then we can add the following to our handlePressMessage method:
messaging/App.js

// ...
handlePressMessage = ({ id, type }) => {
switch (type) {
case 'text':
Alert.alert(
'Delete message?',
'Are you sure you want to permanently delete this message?',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Delete',
style: 'destructive',
onPress: () => {
const { messages } = this.state;
this.setState({ messages: messages.filter(message => message.id !== id\
) });
},
},
],
);
break;
default:
break;
}
};
// ...

After adding these lines, save App.js. When we tap a text message, we should see something like
this:

Core APIs, Part 1

250

Pressing “Delete” will remove the message from the list. We do this by filtering the list of messages
and removing the message with the id that we tapped. The removal currently isn’t animated, but it
will be when we’re finished with this app!
It’s fairly easy to call Alert incorrectly. If you’re coming from the web, you might attempt
to call Alert() rather than Alert.alert. You also might try to call Alert.alert with
parameters that are numbers instead of strings, e.g. our message id. Both of these will crash
the app with confusing error messages. It’s also possible to get into a corrupted state, where
you’ll have to restart the app before Alert.alert will function properly again.

Deleting text messages is useful, but what should we do when the user taps other kinds of messages?
For images, let’s show the image fullscreen.

Fullscreen image
When the user presses an image, we’ll show it fullscreen.

Core APIs, Part 1

251

Transitioning to fullscreen might be accomplished using a navigation library (which we’ll cover in
a later chapter), but we can also do it manually. If we do, we’ll want the Android back button to
dismiss the fullscreen image – we can use the BackHandler API to accomplish this. Let’s also dismiss
the image when it’s pressed again, so that we’re not trapped in a fullscreen image state on iOS.
In App.js, we’ll use state to keep track of which image was pressed. Let’s add the following for
state tracking:
messaging/App.js

// ...
state = {
// ...
fullscreenImageId: null,
};
dismissFullscreenImage = () => {
this.setState({ fullscreenImageId: null });
};
// ...

Core APIs, Part 1

252

We’ve initialized fullscreenImageId to null to indicate that we don’t want to show any image.
Then, when a message is pressed, we’ll set it to the id of the message object. We can update
handlePressMessage with the following:
messaging/App.js

// ...
handlePressMessage = ({ id, type }) => {
switch (type) {
case 'text':
// ...
case 'image':
this.setState({ fullscreenImageId: id });
break;
default:
break;
}
};
// ...

Now we need to render the fullscreen image. We’ll use a TouchableHighlight for the black overlay
background so we can tap it to dismiss the image. Within that, we’ll use an Image. We’ll use the
fullscreenImageId to look up which image we need to display as the uri of the Image component.
First, let’s import Image and TouchableHighlight:
messaging/App.js

import {
// ...
Image,
TouchableHighlight,
} from 'react-native';

Then we can set up the styles:

Core APIs, Part 1

253

messaging/App.js

const styles = StyleSheet.create({
// ...
fullscreenOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'black',
zIndex: 2,
},
fullscreenImage: {
flex: 1,
resizeMode: 'contain',
},
});

We’ll use the built-in StyleSheet.absoluteFillObject so that our overlay background is fullscreen,
and then we’ll add zIndex: 2 so that it renders on top of the rest of our UI. Our image should fill
the overlay, so we use flex: 1.
Next, let’s create a helper method for rendering the fullscreen image called renderFullscreenImage.
We can also use this method to determine if we need to show an image. We’ll call this from render.
Add the following:
messaging/App.js

// ...
renderFullscreenImage = () => {
const { messages, fullscreenImageId } = this.state;
if (!fullscreenImageId) return null;
const image = messages.find(message => message.id === fullscreenImageId);
if (!image) return null;
const { uri } = image;
return (



);

Core APIs, Part 1

254

};
// ...
render() {
return (


{this.renderMessageList()}
{this.renderToolbar()}
{this.renderInputMethodEditor()}
{this.renderFullscreenImage()}

);
}

Save App.js. Now when we tap an image, we should see it fullscreen, and we can tap it again to
dismiss it:

On Android though, we’ll also want the device’s back button to dismiss the image. We do this using
the BackHandler API.

Core APIs, Part 1

255

BackHandler
If you’re not using an Android, you can skip this section!

Like with NetInfo, we use the event listener pattern to handle back button press events:
`BackHandler.addEventListener('hardwareBackPress', handlerFunction);`

We can use this to get notified every time the user presses the back button on an Android device.
We’ll have our handlerFunction hide our fullscreen image.
We can return true from our handlerFunction to indicate that we’ve handled the back button. By
returning false, we indicate that we didn’t handle the event. Therefore, if any other functions have
been registered, the next one registered should be called. These functions are called in the reverse of
the order they were registered – the last handler registered will be called first. If no handler returns
true, then the back button will exit to the home screen (the default back button behavior).
First, import the BackHandler API:
messaging/App.js

import {
// ...
BackHandler,
} from 'react-native';

Then we’ll use componentWillMount and componentWillUnmount to listen to back button presses:
messaging/App.js

// ...
componentWillMount() {
this.subscription = BackHandler.addEventListener('hardwareBackPress', () => {
const { fullscreenImageId } = this.state;
if (fullscreenImageId) {
this.dismissFullscreenImage();
return true;
}
return false;
});

Core APIs, Part 1

256

}
componentWillUnmount() {
this.subscription.remove();
}
// ...

If state.fullscreenImageId exists, then we’re currently showing a fullscreen image, so we’ll want
the back button to dismiss it. We return true to indicate that we shouldn’t exit the app. If we’re not
showing a fullscreen image, we return false. Because no other handlers should be registered, this
will allow for the default back button behavior (exiting the app).
Our message list is working pretty well now! We’ve used two core APIs, Alert and BackHandler,
to create cross-platform interactions for deleting and enlarging messages. Now that we’ve finished
with the message list, let’s move on to the next section of the UI and create the toolbar.

Toolbar
The toolbar will sit above the keyboard and contain an input field for typing messages, along with
buttons for switching to an image picker and sending a location.

Core APIs, Part 1

257

Building the Toolbar
The toolbar is similar to the CommentInput from the Core Components chapter: it maintains the
state of a TextInput field internally and uses an onSubmit function prop to tell the parent when the
message is ready to send. We’ll need a little more control over the focus state of the TextInput this
time, so we’ll use an isFocused prop to control the focus state and an onChangeFocus function prop
to tell the parent when the state should changes.
Let’s create a new file Toolbar.js in the components directory, and render the top level View which
will contain all the elements in the toolbar. We’ll add propTypes for the focus state and the various
functions which the parent component can pass in:

Core APIs, Part 1

messaging/components/Toolbar.js

import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
export default class Toolbar extends React.Component {
static propTypes = {
isFocused: PropTypes.bool.isRequired,
onChangeFocus: PropTypes.func,
onSubmit: PropTypes.func,
onPressCamera: PropTypes.func,
onPressLocation: PropTypes.func,
};
static defaultProps = {
onChangeFocus: () => {},
onSubmit: () => {},
onPressCamera: () => {},
onPressLocation: () => {},
};
render() {
return (

{/* ... */}

);
}
}
const styles = StyleSheet.create({
toolbar: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
paddingHorizontal: 10,
paddingLeft: 16,
backgroundColor: 'white',
},
// ...
});

258

Core APIs, Part 1

259

Let’s add a camera button and a location button and use them to call the onPressCamera and
onPressLocation props.
1

// ...

2
3
4
5
6
7

const ToolbarButton = ({ title, onPress }) => (

{title}

);

8
9
10
11
12

ToolbarButton.propTypes = {
title: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
};

13
14
15

export default class Toolbar extends React.Component {
// ...

16

render() {
const { onPressCamera, onPressLocation } = this.props;

17
18
19

return (

{/* Use emojis for icons instead! */}


{/* ... */}

);

20
21
22
23
24
25
26
27

}

28
29

}

30
31
32
33
34
35
36
37
38
39
40

const styles = StyleSheet.create({
// ...
button: {
top: -2,
marginRight: 12,
fontSize: 20,
color: 'grey',
},
// ...
});

Core APIs, Part 1

260

We can use emojis here for button icons! They require some positioning tweaks in styles.button,
but look decent on both platforms. Later we could swap these out for images or an icon font.
Unfortunately our PDF-creation software doesn’t handle emojis very well, so we couldn’t
include them in the code snippet. You’ll have to grab them from the sample code or choose
emojis of your own!

We define a ToolbarButton component at the top of the file which we can use in the toolbar. This
component is fairly small and styled specifically for use in the toolbar, so we leave it in the same
file as Toolbar. In terms of coding style, it’s common to define small utility components in the same
file that they’re used, and move them into separate files later if we want to reuse them.
Now let’s render a TextInput. As a reminder from the Core Components chapter: when we style a
TextInput, it’s often easier to put styles like border and padding on a wrapper View. Otherwise we
tend to run into slight rendering inconsistencies, e.g. borders not rendering.
1

// ...

2
3
4

export default class Toolbar extends React.Component {
// ...

5
6
7
8

state = {
text: '',
};

9
10
11
12

handleChangeText = (text) => {
this.setState({ text });
};

13
14
15
16

handleSubmitEditing = () => {
const { onSubmit } = this.props;
const { text } = this.state;

17
18

if (!text) return;

19
20
21
22

onSubmit(text);
this.setState({ text: '' });
};

23
24
25
26
27

render() {
const { onPressCamera, onPressLocation } = this.props;
const { text } = this.state;

Core APIs, Part 1

return (

{/* Use emojis for icons instead! */}






);

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

}

47
48

261

}

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

const styles = StyleSheet.create({
// ...
inputContainer: {
flex: 1,
flexDirection: 'row',
borderWidth: 1,
borderColor: 'rgba(0,0,0,0.04)',
borderRadius: 16,
paddingVertical: 4,
paddingHorizontal: 12,
backgroundColor: 'rgba(0,0,0,0.02)',
},
input: {
flex: 1,
fontSize: 18,
},
});

Just like in the previous chapter, we store the value of the input field as state.text. When the user
presses the return key on the keyboard, we call onSubmit with this value and then reset state.text.
Our messaging app doesn’t allow sending multiline messages (we’re using the return key to submit,

Core APIs, Part 1

262

so there’s no way to insert a newline).
We use blurOnSubmit={false} so that the keyboard isn’t dismissed when the user presses the return
key. This is common in messaging apps, since it allows sending multiple messages in a row more
easily.
We’ll need more control over the input field’s internal state than we did in the previous chapter.
We’ll need to focus and blur the input field at specific times. We need to do this because when the
user presses the camera icon, we want to dismiss the keyboard. The built-in React Native APIs for
this are imperative: we have to call .focus() and .blur() on an instance of TextInput. We’ll contain
this complexity in this component, so that App can use an isFocused prop to declare the focus state.
In order to do this, we’ll use a ref prop.
Refs
React let’s us access the instance of any component we render using a ref prop. This is a special
prop that we can supply a callback – the callback will be called with the instance as a parameter,
after the component mounts (and before it unmounts). We can store a reference to the component
instance.
You can think of a component instance as the “this” when we access this.props or any method
that’s part of our class. In this case, the TextInput component class has a focus and blur method
that can be called from the component instance. We can call these from within the lifecycle of our
custom component to control the focus state of the TextInput.
Storing a ref
Let’s capture a reference to the TextInput element we render.
We’ll store this reference as this.input. We can then use this reference to imperatively focus and
blur the input field with this.input.focus() and this.input.blur() when the isFocused prop
changes.
1

// ...

2
3
4

export default class Toolbar extends React.Component {
// ...

5
6
7
8

setInputRef = (ref) => {
this.input = ref;
};

9
10
11
12

componentWillReceiveProps(nextProps) {
if (nextProps.isFocused !== this.props.isFocused) {
if (nextProps.isFocused) {

Core APIs, Part 1

this.input.focus();
} else {
this.input.blur();
}

13
14
15
16

}

17
18

}

19
20
21

handleFocus = () => {
const { onChangeFocus } = this.props;

22
23
24

onChangeFocus(true);
};

25
26
27

handleBlur = () => {
const { onChangeFocus } = this.props;

28
29
30

onChangeFocus(false);
};

31
32

// ...

33
34
35

render() {
const { onPressCamera, onPressLocation } = this.props;

36
37
38

// Grab this from state!
const { text } = this.state;

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

return (

{/* Use emojis for icons instead! */}






);

56
57
58
59
60
61
62

}

63
64

264

}

The onFocus prop of the TextInput will be called when the user taps within the input field, and
the onBlur prop will be called when the user taps outside the input field. We use handleFocus and
handleBlur to notify the parent of changes to the focus state.
Whenever the parent passes a different value for the isFocused prop, we update the focus state of the
TextInput by calling this.input.focus() or this.input.blur() in componentWillReceiveProps.
Go ahead and save Toolbar.js. We can now control the focus state of the toolbar entirely from App
using isFocused and onChangeFocus. We won’t use this much in the next section, but it’ll be very
important when working with the keyboard near the end of the chapter.

Adding Toolbar to App
Let’s now render our Toolbar component from App.js. We can handle the onSubmit event to
populate our message list with real messages. When we type in the input field and submit the text (by
pressing the return key on the keyboard), we can add a new message to the messages in state using
our createTextMessage utility function. We’ll also add a few callback functions as placeholders.
messaging/App.js

import Toolbar from './components/Toolbar';
// ...
export default class App extends React.Component {
state = {
// ...
isInputFocused: false,
}
handlePressToolbarCamera = () => {
// ...
}
handlePressToolbarLocation = () => {

Core APIs, Part 1

265

// ...
}
handleChangeFocus = (isFocused) => {
this.setState({ isInputFocused: isFocused });
};
handleSubmit = (text) => {
const { messages } = this.state;
this.setState({
messages: [createTextMessage(text), ...messages],
});
};
renderToolbar() {
const { isInputFocused } = this.state;
return (



);
}
// ...
}
// ...

We’ll store isInputFocused in this.state to keep track of the focus state of the TextInput in toolbar.
After saving App.js, our toolbar should look like this:

Core APIs, Part 1

266

There’s an interesting edge case when we open a fullscreen image preview while the input field is
focused – the keyboard stays up even though the input field is no longer visible!

Core APIs, Part 1

267

We can address this by updating our handlePressMessage function to also set isInputFocused:
false in the component’s state.
messaging/App.js

// ...
handlePressMessage = ({ id, type }) => {
switch (type) {
// ...
case 'image':
this.setState({ fullscreenImageId: id, isInputFocused: false });
break;
default:
break;
}
};
// ...

Now the keyboard should be dismissed when we tap an image to preview it fullscreen.

Core APIs, Part 1

268

Next, let’s connect the location button so we can send messages containing a map with a pin at our
location.

Geolocation
The React Native geolocation API is slightly different than other APIs: we can access it directly
from the global navigator object, rather than importing it at the top of the file.
The geolocation API in React Native is the same as the one found in modern web browsers. This
means better compatibility between libraries and a lower learning curve if you’re coming from web
development. On the web, the navigator object contains a lot of useful metadata about your web
browser. In React Native, it’s really just a container for geolocation and potentially a handful of
other browser APIs. Accessing a global variable feels a bit unusual in React Native, but is necessary
to provide the exact same API on web and mobile.

We’ll use navigator.geolocation.getCurrentPosition to get our current position. This API takes
a callback parameter which is called with an object containing our coordinates, coords, in latitude
and longitude.
There’s currently a bug in Expo/React Native. This method never calls its callback parameter
on Android. We’ll update this chapter as soon as it’s fixed!

Let’s try it out. We can get our current position and use it to create a location message in the
MessageList. Add the following to handlePressToolbarLocation in App.js:
1

// ...

2
3
4

handlePressToolbarLocation = () => {
const { messages } = this.state;

5
6
7

navigator.geolocation.getCurrentPosition((position) => {
const { coords: { latitude, longitude } } = position;

8
9
10
11
12
13
14

this.setState({
messages: [
createLocationMessage({
latitude,
longitude,
}),

Core APIs, Part 1
15
16
17
18
19

269

...messages,
],
});
});
};

20
21

// ...

Pretty simple!
If you try it out, you may be prompted to give Expo permission to access your location. Expo is
already set up to allow the location permission.
If you’re building an app using react-native-cli, you’ll also need to modify your
Info.plist on iOS and AndroidManifest.xml on Android to enable location permissions.

Tapping the location button should now add a location message:

Depending on how we’re using geolocation, there are a few other APIs that might be useful: - watchPosition(success, error?, options?) and clearWatch(watchID) can be used to
receive notifications when location changes. We can also pass the options timeout (number in

Core APIs, Part 1

270

ms), maximumAge (number in ms), and enableHighAccuracy (bool) for more granular control. requestAuthorization() can be used to request access to device location. This can be a better experience than presenting an alert when a map is shown for the first time. - getCurrentPosition(geo_success, geo_error?, geo_options?) is the full function signature of the getCurrentPosition API
we use above. Although we didn’t do it in our example, we would generally want to handle errors
and present them to the user in some way. We might also want to pass options for more granular
control (the same options as watchPosition).

Input Method Editor (IME)
We’ve finished creating the message list and toolbar. Let’s move on to one of the more interesting
features of our app: the custom input method editor for sending images.

We’ll build the image grid flexibly so we could easily use it in other apps with just a few
modifications.

Image picker
Let’s populate our image grid with photos from the camera roll. To do this, we’ll need to access the
images saved on the device and display them in an infinitely scrolling grid. We can do this with a
combination of the Core APIs CameraRoll, Dimensions, and PixelRatio.

Core APIs, Part 1

271

Building a grid
Let’s make a new Grid component which we’ll use to display Image components from the camera
roll.
Here we’ll use a FlatList to make our image grid. If you recall from the previous chapter, the
FlatList component is a feature-packed ScrollView that we can use for infinite scrolling out-ofthe-box. The FlatList can be configured to display multiple columns, instead of the normal single
column, by passing the numColumns prop.
Let’s make a new file Grid.js within the components directory. In this, we can create a Grid
component which renders a FlatList. Our Grid will be a wrapper around FlatList that configures
it specifically for our use case: displaying multiple columns of square-shaped components.
Our Grid will pass along all of its props to FlatList, since it’s mostly acting as a more specific case of
the very general FlatList component. Our Grid component will handle three props (two of which
are also FlatList props):
• renderItem - A function called with each item. Should return a React element. We want to
intercept this function before passing it to FlatList. We’re going to call it with the same
arguments that FlatList would call it with, but we’re going to add some extra information
about the style of the item to render.
• numColumns - The number of columns per row. We’ll use this to calculate item dimensions.
We’ll pass this to FlatList directly.
• itemMargin - The vertical and horizontal spacing between each item in the grid. This prop
doesn’t need to be passed into FlatList.
Here’s a skeleton for the Grid component code:
messaging/components/Grid.js

import { Dimensions, FlatList, PixelRatio, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
export default class Grid extends React.Component {
static propTypes = {
renderItem: PropTypes.func.isRequired,
numColumns: PropTypes.number,
itemMargin: PropTypes.number,
};
static defaultProps = {
numColumns: 4,
itemMargin: StyleSheet.hairlineWidth,

Core APIs, Part 1

272

};
renderGridItem = (info) => {
// ... The interesting stuff happens here!
};
render() {
return ;
}
}

Note that we pass all props into FlatList. We’re building a wrapper around FlatList that adds some
extra rendering info to each item, but we’re going to let FlatList do the hard work of rendering
rows of content in an infinitely scrolling list. We’ll focus on rendering square Image components of
equal size in the renderGridItem function.
We’ll need to get the dimensions of the device using the Dimensions API.
messaging/components/Grid.js

renderGridItem = (info) => {
// ...
const { width } = Dimensions.get('window');
// ...
};

At first glance, it might look like we should call Dimensions.get('window') outside of the render
path, so we only have to retrieve it once. However, width can change sizes depending on device
orientation, multitasking mode, etc, so it’s best to do this within the render path.

Next, let’s calculate the dimensions of each item we’ll render. Here we’ll want to use the PixelRatio.roundToNearest
API. This API helps us ensure we align content to physical pixels when we’re dealing with noninteger dimensions.
In React Native, we specify dimensions in terms of logical pixels rather than physical
pixels. There may be multiple physical pixels per logical pixels in a device with a high
pixel density, e.g. retina display. When we make calculations that can result in non-integer
dimensions, we should use PixelRatio to help us align to the nearest physical pixel otherwise, there may be visual inconsistencies (e.g. some elements or margins appear
larger than others).

Core APIs, Part 1

273

messaging/components/Grid.js

renderGridItem = (info) => {
// ...
const { numColumns, itemMargin } = this.props;
const { width } = Dimensions.get('window');
const size = PixelRatio.roundToNearestPixel(
(width - itemMargin * (numColumns - 1)) / numColumns,
);
// ...
};

We now have a pixel-aligned size for each item in the grid. Let’s also calculate the margins between
elements. We can use the index of the item, which is passed to us automatically by the FlatList.
messaging/components/Grid.js

renderGridItem = (info) => {
const { index } = info;
const { numColumns, itemMargin } = this.props;
const { width } = Dimensions.get('window');
const size = PixelRatio.roundToNearestPixel(
(width - itemMargin * (numColumns - 1)) / numColumns,
);
// We don't want to include a `marginLeft` on the first item of a row
const marginLeft = index % numColumns === 0 ? 0 : itemMargin;
// We don't want to include a `marginTop` on the first row of the grid
const marginTop = index < numColumns ? 0 : itemMargin;
// ...
};

Great! We’ve done all the calculations necessary to start rendering Image elements of the appropriate
size. Let’s call the renderItem prop with this information.

Core APIs, Part 1

274

messaging/components/Grid.js

renderGridItem = (info) => {
const { renderItem, numColumns, itemMargin } = this.props;
// ...
return renderItem({ ...info, size, marginLeft, marginTop });
};

We augment the info passed by FlatList with size, marginLeft, and marginTop, so that we can
render items at the correct size from within the renderItem function.

On generic vs. specific components
What we just did was a little complicated: we created a Grid component which accepts a renderItem
function prop, then we passed a different function, renderGridItem, into the FlatList.
Our goal here is to take the very powerful and generic FlatList and create a more specific version
called Grid. We want to expose nearly all the customizability of FlatList (we propagate all the
props from Grid into FlatList), while tweaking a few parts to make rendering in a grid format
more straightforward. By keeping the API as similar as possible to FlatList, our Grid can be used
as an almost drop-in replacement. Additionally, the learning curve for using our Grid is much lower
than a completely custom component, since if we know the API of FlatList we also know the API
of Grid.

Adding images to the grid
Our grid is ready to go! We wrote the Grid component so that we could add an infinitely scrolling
grid of images for the user to send as messages.
Ultimately we want to fill this grid with photos from the camera roll. But first, let’s try using it with
some placeholder images to test it out.
Create a new file in components called ImageGrid.js. We’ll use our Grid component by adding the
following code:

275

Core APIs, Part 1

messaging/components/ImageGrid.js
import { CameraRoll, Image, StyleSheet, TouchableOpacity } from 'react-native';

import { Permissions } from 'expo';
import PropTypes from 'prop-types';
import React from 'react';
import Grid from './Grid';
const keyExtractor = ({ uri }) => uri;
export default class ImageGrid extends React.Component {
static propTypes = {
onPressImage: PropTypes.func,
};
static defaultProps = {
onPressImage: () => {},
};
state = {
images: [
{ uri: 'https://picsum.photos/600/600?image=10'
{ uri: 'https://picsum.photos/600/600?image=20'
{ uri: 'https://picsum.photos/600/600?image=30'
{ uri: 'https://picsum.photos/600/600?image=40'
],
};

},
},
},
},

renderItem = ({ item: { uri }, size, marginTop, marginLeft }) => {
const style = {
width: size,
height: size,
marginLeft,
marginTop,
};
return (

);
};
render() {
const { images } = this.state;

Core APIs, Part 1

276

return (

);
}
}
const styles = StyleSheet.create({
image: {
flex: 1,
},
});

You can see that we use Grid in almost the same way as we would use a FlatList. The difference is
that renderItem has a few additional values we can use for layout: size, marginTop, and marginLeft.
Save ImageGrid.js. Let’s render ImageGrid from App.js to see what we have so far. Update App.js
with the following:
messaging/App.js

// ...
import ImageGrid from './components/ImageGrid';
export default class App extends React.Component {
// ...
renderInputMethodEditor = () => (



);
// ...
}
// ...

Core APIs, Part 1

277

When we save App.js, we should see something similar to this:

On separating components
Notice how by making a separate Grid component, we’ve cleanly separated the grid rendering logic
from the content we render. We could have written the grid rendering logic, and the image loading
from the camera roll in the same ImageGrid component, but then the component would’ve had two
reasonably complex and distinct tasks.
As a general guideline for React Native, it’s useful to separate complex concerns (e.g. rendering
calculations, data fetching) into separate components, so that our components remain focused on a
single task. This makes them easier understand when reading them later, and easier to reuse. Our
Grid can easily be reused in other apps with other kinds of content. Our ImageGrid could render
images into a FlatList instead of a Grid with very few changes.

Loading images from the camera roll
Let’s replace the placeholder images we’ve added to state.images with real images from the camera
roll.

Core APIs, Part 1

278

We can use CameraRoll.getPhotos(options) to request an array of images from the device. We can
specify the number of images we want to get with the first option. We can use a cursor to iterate
through the list of images by passing an after option (more on this soon). This API is asynchronous
and returns a promise containing the image metadata, along with pagination info.
Since this API is asynchronous, it may take some time for the first images to be returned. The more
images we request, the longer it will take. It’s best to request just enough images to fill the entire
screen: we want the API response as soon as we can, but we also want the screen to load all at once,
rather than piecemeal.
Calling CameraRoll.getPhotos(options) returns a promise, which resolves to an object containing:
• edges - An array of items, each containing a node object. The node object contains metadata
about the image, such as timestamp and location. The node object also contains an image object
with the filename, width, height, and uri of the image.
• page_info - An object containing a boolean has_next_page, a string end_cursor, and a string
before_cursor.
We can pass end_cursor to the after option of CameraRoll.getPhotos(options) in order to iterate
through the list.
If you’re not familiar with using cursors, they’re a common way to iterate through lists
of data stored on servers or in databases. A cursor points to a specific item in a list. By

Core APIs, Part 1

279

passing the cursor along with a request for more items, the server or database will know
which items it’s already returned to you and which it should return next. The details
aren’t too relevant for our uses, but if you’re curious about cursors, you can read more
here63 .
Before we can access the camera roll on Android, we’ll need to request the user’s permission to do
so. We can use the Expo Permissions API to do this. We can await a call to Permissions.askAsync
containing the permission we want access to, and check the returned object for whether request was
granted.
await Permissions.askAsync(Permissions.CAMERA_ROLL);
if (status !== 'granted') {
// Denied
} else {
// Good to go!
}

More info about permissions is available in the Expo docs64 .

In componentDidMount, let’s get camera roll permissions, load an initial set of images, and store the
images in state.images.
messaging/components/ImageGrid.js

// ...
export default class ImageGrid extends React.Component {
state = {
images: [],
};
componentDidMount() {
this.getImages();
}
getImages = async () => {
const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL);
63 https://en.wikipedia.org/wiki/Cursor_(databases)
64 https://docs.expo.io/versions/latest/sdk/permissions.html

Core APIs, Part 1

280

if (status !== 'granted') {
console.log('Camera roll permission denied');
return;
}
const results = await CameraRoll.getPhotos({
first: 20,
});
const { edges } = results;
const loadedImages = edges.map(item => item.node.image);
this.setState({ images: loadedImages });
};
// ...
}

This should give us at most 20 images. If there are fewer than 20 images saved on the device, then
we may see fewer.
We make our getImages method async so that we can use await with CameraRoll.getPhotos.
This works well for 20 images, but now we need to load more when the user scrolls to the bottom
of the Grid. We can use the onEndReached function prop of FlatList (which is also a prop of Grid)
to notify us that we need to load more images. This is trickier than it sounds: the onEndReached
function we pass may be called multiple times before we have finished loading a new set of images.
We need to be careful not to load the same set of images twice. Let’s start by calling getNextImages
when we reach the end of the list:
messaging/components/ImageGrid.js

// ...
export default class ImageGrid extends React.Component {
// ...
getNextImages = () => {
// ...
};
// ...

Core APIs, Part 1

281

render() {
const { images } = this.state;
return (

);
}
}

We can use the page_info object in the response of CameraRoll.getPhotos to determine if we need
to load another page. We’ll use:
• has_next_page - Are there more images to load?
• end_cursor - The cursor we can use to load more images after the current set we’ve just
retrieved.
Let’s keep track of the internal state of the pagination with two member variables, this.loading and
this.cursor. We don’t need to put these in this.state since they don’t directly affect component
rendering. We can also update them synchronously which will make our implementation simpler.
Anytime we use this.setState we have to keep in mind that it occurs asynchronously.
messaging/components/ImageGrid.js

// ...
export default class ImageGrid extends React.Component {
loading = false;
cursor = null;
// ...
getNextImages = () => {
if (!this.cursor) return;
this.getImages(this.cursor);
};
getImages = async (after) => {

Core APIs, Part 1

282

if (this.loading) return;
this.loading = true;
const results = await CameraRoll.getPhotos({
first: 20,
after,
});
const { edges, page_info: { has_next_page, end_cursor } } = results;
const loadedImages = edges.map(item => item.node.image);
this.setState(
{
images: this.state.images.concat(loadedImages),
},
() => {
this.loading = false;
this.cursor = has_next_page ? end_cursor : null;
},
);
};
}

By keeping track of loading, we can be certain that we’ll only ever load one set of images at a time.
We set this.loading = true before making the asynchronous call to getPhotos, and we wait till
the asynchronous call to this.setState() has completed before setting this.loading = false.
The second parameter of this.setState is a completion callback. We can use this to avoid race
conditions between the time we call this.setState and the time this.state is actually updated.
If we didn’t use the completion callback and instead set this.loading = false after calling
this.setState, we would potentially access this.state.images before it had been updated, thus
one set of the images we loaded would fail to be added to the list.
We abort getNextImages if this.cursor doesn’t have a value. This stops us from loading the initial
set of images again once we reach the end of the camera roll. If we preferred, we could instead record
a boolean this.hasNextPage to help us track when we’ve reached the end.
The last thing we’ll do in this file is call the onPressImage prop whenever we tap an image. We’ll
pass the uri of the image so that we can use it within messages.
We’ll wrap the Image in a TouchableOpacity in order to handle press events. Update renderItem
with the following:

Core APIs, Part 1

283

messaging/components/ImageGrid.js

// ...
renderItem = ({ item: { uri }, size, marginTop, marginLeft }) => {
const { onPressImage } = this.props;
const style = {
width: size,
height: size,
marginLeft,
marginTop,
};
return (
 onPressImage(uri)}
style={style}
>


);
};
// ...

Save ImageGrid.js and let’s start using it to add messages to the message list!

Sending images from ImageGrid
We can now load images from the camera roll and display them in a pixel-perfect grid. When
we tap an image, let’s add a new image message to the list of messages in state using our
createImageMessage utility function.
We’ll use handlePressImage to add a new image to the message list:

Core APIs, Part 1

284

messaging/App.js

// ...
export default class App extends React.Component {
// ...
handlePressImage = (uri) => {
const { messages } = this.state;
this.setState({
messages: [createImageMessage(uri), ...messages],
});
};
renderInputMethodEditor = () => (



);
// ...
}

Great! We can now tap images and they’ll appear in the MessageList we wrote earlier. Here’s what
it should look like:

Core APIs, Part 1

285

What we’ve built
At this point, we have the bulk of the UI components written. In order to access device information, we’ve used a variety of APIs including Alert, CameraRoll, Dimensions, Geolocation,
NetInfo,PixelRatio, and StatusBar.
Using React Native APIs tends to follow a pattern:
1.
2.
3.
4.
5.

Figure out which API we need to call.
Figure out which lifecycle/helper method is the most appropriate place to call it.
Call the API (either synchronously or asynchronously).
Store the results in component state.
Re-render the UI based on the new state.

We can use this approach with nearly any API. We’ll continue covering other APIs in the second
part of this chapter, and in the rest of the book.

Core APIs, Part 2
In the first part of this section, we covered a variety of React Native APIs for accessing device
information. In this part, we’ll focus on one fundamental feature of mobile devices: the keyboard.
This is a code checkpoint. If you haven’t been coding along with us but would like to start
now, we’ve included a snapshot of our current progress in the sample code for this book.
If you haven’t created a project yet, you’ll need to do so with:
$ create-react-native-app messaging --scripts-version 1.14.0

Then, copy the contents of the directory messaging/1 from the sample code into your new
messaging project directory.

The keyboard
Keyboard handling in React Native can be very complex. We’re going to learn how to manage the
complexity, but it’s a challenging problem with a lot of nuanced details.
Our UI is currently a bit flawed: on iOS, when we focus the message input field, the keyboard opens
up and covers the toolbar. We have no way of switching between the image picker and the keyboard.
We’ll focus on fixing these issues.
We’re about to embark on a deep dive into keyboard handling. We’ll cover some extremely
useful APIs and patterns – however, you shouldn’t feel like you have to complete the entire
chapter now. Feel free to stop here and return again when you’re actively building a React
Native app that involves the keyboard.

Why it’s difficult
Keyboard handling can be challenging for many reasons:
• The keyboard is enabled, rendered, and animated natively, so we have much less control over
its behavior than if it were a component (where we control the lifecycle).
• We have to handle a variety of asynchronous events when the keyboard is shown, hidden,
or resized, and update our UI accordingly. These events are somewhat different on iOS and
Android, and even slightly different in the simulator compared to a real device.

Core APIs, Part 2

287

• The keyboard works differently on iOS and Android at a fundamental level. On iOS, the
keyboard appears on top of the existing UI; the existing UI doesn’t resize to avoid the keyboard.
On Android, the keyboard resizes the UI above it; the existing UI will shrink to fit in the
available space. We generally want interactions to feel similar on both platforms, despite this
fundamental difference.
• Keyboards interact specially with certain native elements e.g. ScrollView. On iOS, dragging
downward on a ScrollView can dismiss the keyboard at the same rate of the pan gesture.
• Keyboards are user-customizable on both platforms, meaning there’s an almost unlimited
number of shapes and sizes our UI has to handle.
In this app, we’ll attempt to achieve a native-quality messaging experience. Ultimately though, there
will be a few aspects that don’t quite feel native. It’s extremely difficult to get an app with complex
keyboard interactions to feel perfect without dropping down to the native level. If you can’t achieve
the right experience in React Native, consider writing a native module for the screen that interacts
heavily with the keyboard. This is part of the beauty of React Native – you can start with a JavaScript
version in your initial implementation of a screen or feature, then seamlessly swap it out for a native
implementation when you’re certain it’s worth the time and effort.
If you’re lucky, you’ll be able to find an existing open source native component that does
exactly that!

KeyboardAvoidingView
In the first chapter, we demonstrated how to use the KeyboardAvoidingView component to move
the UI of the app out from under the keyboard. This component is great for simple use cases, e.g.
focusing the UI on an input field in a form.
When we need more precise control, it’s often better to write something custom. That’s what we’ll
do here, since we need to coordinate the keyboard with our custom image input method.
Our goal here is for our image picker to have the same height as the native keyboard, in essence
acting as a custom keyboard created by our app. We’ll want to smoothly animate the transition
between these two input methods.
For a demo of the desired behavior, you can try playing around with the completed app (it’s the
same app as the previous section):
• On Android, you can scan this QR code from within the Expo app:

Core APIs, Part 2

288

• On iOS, you can build the app and preview it on the iOS simulator or send the link of the
project URL to your device like we’ve done in previous chapters.

On managing complexity
Since this problem is fairly complicated, we’re going to break it down into 3 parts, each with its own
component:
• MeasureLayout - This component will measure the available space for our messaging UI
• KeyboardState - This component will keep track of the keyboard’s visibility, height, etc
• MessagingContainer - This component will displaying the correct IME (text, images) at the
correct size
We’ll connect them so that MeasureLayout renders KeyboardState, which in turn renders MessagingContainer.
We could build one massive component that handles everything, but this would get very complicated
and be difficult to modify or reuse elsewhere.

Keyboard
We’ll need to measure the available space on the screen and the keyboard height ourselves, and
adjust our UI accordingly. We’ll keep track of whether the keyboard is currently transitioning. And
we’ll animate our UI to transition between the different keyboard states.
To do this, we’ll use the Keyboard API. The Keyboard API is the lower-level API that KeyboardAvoidingView
uses under the hood.
On iOS, the keyboard uses an animation with a special easing curve that’s hard to replicate
in JavaScript, so we’ll hook into the native animation directly using the LayoutAnimation API.
LayoutAnimation is one of the two main ways to animate our UI (the other being Animated). We’ll
cover animation more in a later chapter.

Core APIs, Part 2

289

Measuring the available space
Let’s start by measuring the space we have to work with. We want to measure the space that our
MessageList can use, so we’ll measure from below the status bar (anything above our MessageList)
to the bottom of the screen. We need to do this to get a numeric value for height, so we can transition
between the height when the keyboard isn’t visible to the height when the keyboard is visible. Since
the keyboard doesn’t actually take up any space in our UI, we can’t rely on flex: 1 to take care of
this for us.
Measuring in React Native is always asynchronous. In other words, the first time we render our UI,
we have no general-purpose way of knowing the height. If the content above our MessageList has
a fixed height, we can calculate the initial height by taking Dimensions.get('window').width and
subtracting the height of the content above our MessageList – however, this is not very flexible.
Instead, let’s create a container View with a flexible height flex: 1 and measure it on first render.
After that, we’ll always have a numeric value for height.
We can measure this View with the onLayout prop. By passing a callback to onLayout, we can get
the layout of the View. This layout contains values for x, y, width, and height.
messaging/components/MeasureLayout.js
1
2
3
4

import
import
import
import

{ Constants } from 'expo';
{ Platform, StyleSheet, View } from 'react-native';
PropTypes from 'prop-types';
React from 'react';

5
6
7
8
9

export default class MeasureLayout extends React.Component {
static propTypes = {
children: PropTypes.func.isRequired,
};

10
11
12
13

state = {
layout: null,
};

14
15
16

handleLayout = event => {
const { nativeEvent: { layout } } = event;

17
18
19
20
21
22
23
24

this.setState({
layout: {
...layout,
y:
layout.y +
(Platform.OS === 'android' ? Constants.statusBarHeight : 0),
},

Core APIs, Part 2

290

});
};

25
26
27

render() {
const { children } = this.props;
const { layout } = this.state;

28
29
30
31

// Measure the available space with a placeholder view set to flex 1
if (!layout) {
return ;
}

32
33
34
35
36

return children(layout);

37

}

38
39

}

40
41
42
43
44
45

const styles = StyleSheet.create({
container: {
flex: 1,
},
});

Here we render a placeholder View with an onLayout prop. When called, we update state with the
new layout.
Most React Native components accept an onLayout function prop. This is conceptually
similar to a React lifecycle method: the function we pass is called every time the component
updates its dimensions. We need to be careful when calling setState within this function,
since setState may cause the component to re-render, in which case onLayout will get called
again… and now we’re stuck in an infinite loop!

We have to compensate for the solid color status bar we use on Android by adjusting the y value,
since the status bar height isn’t included in the layout data. We can do this by merging the existing
properties of layout, ...layout, and an updated y value that includes the status bar height.
We use a new pattern here for propagating the layout into the children of this component: we
require the children prop to be a function. When we use our MeasureLayout component, it will
look something like this:

Core APIs, Part 2

291


{layout => }


This pattern is similar to having a renderX prop, where X indicates what will be rendered, e.g.
renderMessages. However, using children makes the hierarchy of the component tree more clear.
Using the children prop implies that these children components are the main thing the parent
renders. As an analogy, this pattern is similar to choosing between export default and export X. If
there’s only one variable to export from a file, it’s generally more clear to go with export default. If
there’s a variable with the same name as the file, or a variable that seems like the primary purpose of
the file, you would also likely export it with export default and export other variables with export
X. Similarly, you should consider using children if this prop is the “default” or “primary” thing a
component renders. Ultimately this is an API style preference. Even if you choose not to use it, it’s
useful to be aware of the pattern since you may encounter it when using open source libraries.

We’re now be able to get a precise height which we can use to resize our UI when the keyboard
appears and disappears.

Keyboard events
We have the initial height for our messaging UI, but we need to update the height when the keyboard
appears and disappears. The Keyboard object emits events to let us know when it appears and
disappears. These events contain layout information, and on iOS, information about the animation
that will/did occur.

KeyboardState
Let’s create a new component called KeyboardState to encapsulate the keyboard event handling
logic. For this component, we’re going to use the same pattern as we did for MeasureLayout: we’ll
take a children function prop and call it with information about the keyboard layout.
We can start by figuring out the propTypes for this component. We know we’re going to have
a children function prop. We’re also going to consume the layout from the MeasureLayout
component, and use it in our keyboard height calculations.

Core APIs, Part 2

292

messaging/components/KeyboardState.js

import { Keyboard, Platform } from "react-native";
import PropTypes from 'prop-types';
import React from 'react';
export default class KeyboardState extends React.Component {
static propTypes = {
layout: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
}).isRequired,
children: PropTypes.func.isRequired,
};
// ...
}

Now let’s think about the state. We want to keep track of 6 different values, which we’ll pass into
the children of this component:
• contentHeight: The height available for our messaging content.
• keyboardHeight: The height of the keyboard. We keep track of this so we set our image picker
to the same size as the keyboard.
• keyboardVisible: Is the keyboard fully visible or fully hidden?
• keyboardWillShow: Is the keyboard animating into view currently? This is only relevant on
iOS.
• keyboardWillHide: Is the keyboard animating out of view currently? This is only relevant on
iOS, and we’ll only use it for fixing visual issues on the iPhone X.
• keyboardAnimationDuration: When we animate our UI to avoid the keyboard, we’ll want to
use the same animation duration as the keyboard. Let’s initialize this with the value 250 (in
milliseconds) as an approximation.

Core APIs, Part 2

293

messaging/components/KeyboardState.js

// ...
const INITIAL_ANIMATION_DURATION = 250;
export default class KeyboardState extends React.Component {
// ...
constructor(props) {
super(props);
const { layout: { height } } = props;
this.state = {
contentHeight: height,
keyboardHeight: 0,
keyboardVisible: false,
keyboardWillShow: false,
keyboardWillHide: false,
keyboardAnimationDuration: INITIAL_ANIMATION_DURATION,
};
}
// ...
}

Now that we’ve determined which properties to keep track of, let’s update them based on keyboard
events.
There are 4 Keyboard events we should listen for:
•
•
•
•

keyboardWillShow (iOS only) - The keyboard is going to appear
keyboardWillHide (iOS only) - The keyboard is going to disappear
keyboardDidShow - The keyboard is now fully visible
keyboardDidHide - The keyboard is now fully hidden

In componentWillMount we can add listeners to each keyboard event and in componentWillUnmount
we can remove them.

Core APIs, Part 2
1

294

// ...

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

componentWillMount() {
if (Platform.OS === 'ios') {
this.subscriptions = [
Keyboard.addListener('keyboardWillShow', this.keyboardWillShow),
Keyboard.addListener('keyboardWillHide', this.keyboardWillHide),
Keyboard.addListener('keyboardDidShow', this.keyboardDidShow),
Keyboard.addListener('keyboardDidHide', this.keyboardDidHide),
];
} else {
this.subscriptions = [
Keyboard.addListener('keyboardDidHide', this.keyboardDidHide),
Keyboard.addListener('keyboardDidShow', this.keyboardDidShow),
];
}
}

18
19
20
21

componentWillUnmount() {
this.subscriptions.forEach(subscription => subscription.remove());
}

22
23

// ...

We’ll add the listeners slightly differently for each platform: on Android, we don’t get events for
keyboardWillHide or keyboardWillShow.
Storing subscription handles in an array is a common practice in React Native. We don’t know
exactly how many subscriptions we’ll have until runtime, since it’s different on each platform, so
removing all subscriptions from an array is easier than storing and removing a reference to each
listener callback.
Let’s use these events to update keyboardVisible, keyboardWillShow, and keyboardWillHide in our
state:
messaging/components/KeyboardState.js

// ...
keyboardWillShow = (event) => {
this.setState({ keyboardWillShow: true });
// ...
};

Core APIs, Part 2

295

keyboardDidShow = () => {
this.setState({
keyboardWillShow: false,
keyboardVisible: true,
});
// ...
};
keyboardWillHide = (event) => {
this.setState({ keyboardWillHide: true });
// ...
};
keyboardDidHide = () => {
this.setState({
keyboardWillHide: false,
keyboardVisible: false
});
};
// ...

The listeners keyboardWillShow, keyboardDidShow, and keyboardWillHide will each be called with
an event object, which we can use to measure the contentHeight and keyboardHeight. Let’s
do that now, using this.measure(event) as a placeholder for the function which will perform
measurements.
messaging/components/KeyboardState.js

// ...
keyboardWillShow = (event) => {
this.setState({ keyboardWillShow: true });
this.measure(event);
};
keyboardDidShow = (event) => {
this.setState({
keyboardWillShow: false,
keyboardVisible: true,
});
this.measure(event);

Core APIs, Part 2

296

};
keyboardWillHide = (event) => {
this.setState({ keyboardWillHide: true });
this.measure(event);
};
// ...

For iOS it would be sufficient to calculate measurements in the keyboardWill* events, since the
keyboardDid* events should receive the same event parameter. However, since Android only
supports the keyboardDid* events, we also need to use keyboardDidShow. Calculating measurements
in keyboardDidShow on iOS shouldn’t affect the app’s behavior, but we could do this conditionally
by checking Platform.OS === 'android' if we preferred.
We can use these events to keep track of the keyboard’s current state. Each event object will have
the following properties:
• duration - Duration of the keyboard animation. In practice, this is typically constant across all
keyboard animations. This property only exists on iOS, so we’ll use a constant to approximate
it on Android.
• easing - Easing curve used by the keyboard animation. This will be the special easing curve
called 'keyboard', which we can use to sync our own animations with the keyboard’s. This
property only exists on iOS, since there isn’t a specific keyboard animation on Android. We’ll
use 'easeInEaseOut' as a pleasant-looking default to approximate the keyboard animation on
Android.
• startCoordinates, endCoordinates - An object containing keys height, width, screenX, and
screenY. These refer to the start and end coordinates of the keyboard. Normally height, width,
and screenX will stay the same. We can use height to determine the height of the keyboard. The
screenY value refers to the top of the keyboard, which we can use to determine the remaining
height available to render content.
To calculate the contentHeight, we can take the screenY (top coordinate of the keyboard) and
subtract layout.y (top coordinate of our messaging component).

Core APIs, Part 2
1
2

297

measure = (event) => {
const { layout } = this.props;

3
4
5

const { endCoordinates: { height, screenY }, duration = INITIAL_ANIMATION_DURATION\
} = event;

6
7
8
9
10
11
12

this.setState({
contentHeight: screenY - layout.y,
keyboardHeight: height,
keyboardAnimationDuration: duration,
});
};

13
14

// ...

Remember, y coordinates lower down on the screen are larger than those higher on the
screen, so this calculation will resulting in a positive value.
Note that if a hardware keyboard is connected, the height of the keyboard will be 0 – we’ll have to
handle this specially later.
Let’s propagate all of these values into the children of this component. We’ll also propagate the
height of the entire component as containerHeight.
messaging/components/KeyboardState.js

// ...
render() {
const { children, layout } = this.props;
const {
contentHeight,
keyboardHeight,
keyboardVisible,
keyboardWillShow,
keyboardWillHide,
keyboardAnimationDuration,
} = this.state;
return children({
containerHeight: layout.height,
contentHeight,
keyboardHeight,
keyboardVisible,

Core APIs, Part 2

298

keyboardWillShow,
keyboardWillHide,
keyboardAnimationDuration,
});
}
// ...

When we use this component, it’ll look roughly like this: we’ll first wrap it in our MeasureLayout
component, and pass the layout in as a prop. We can then render our content using the keyboardInfo
object.

{layout => (

{keyboardInfo => /* ... */}

)}


Alright, we’re almost there! We have MeasureLayout and KeyboardState. The last component we
need is MessagingContainer to render the content using the sizes we’ve calculated.

MessagingContainer
Let’s create a new component MessagingContainer to render the correct Input Method Editor (IME)
at the correct size.
Once again, let’s figure out the propTypes first. This component is going to have a lot of props, since
it’s consuming data from the previous components we wrote, in addition to more props which we’ll
pass in from App.
The main job of this component is to display the correct IME at any given time. Let’s define constants
for each potential state:
• NONE - Don’t show any IME.
• KEYBOARD - The text input is focused, so the keyboard should be visible.
• CUSTOM - Show our custom IME. In this case, we’ll show our image picker, but we could show
other kinds of input here if we wanted to.
Let’s create an object to hold these. We’ll also export it so that other components can easily use the
correct string values.

Core APIs, Part 2

299

messaging/components/MessagingContainer.js

import { BackHandler, LayoutAnimation, Platform, UIManager, View } from 'react-nativ\
e';
import PropTypes from 'prop-types';
import React from 'react';
export const INPUT_METHOD = {
NONE: 'NONE',
KEYBOARD: 'KEYBOARD',
CUSTOM: 'CUSTOM',
};
// ...

Now for the propTypes. We’ll begin by declaring each of the values that will be passed from
KeyboardState. We’ll define inputMethod and onChangeInputMethod to handle switching between
IMEs and notifying the parent of changes. We’ll also support rendering content in both the keyboard
area with renderInputMethodEditor and the main content area with children. In this case, children
should be a normal React element rather than a function like in the two components we wrote
previously.
messaging/components/MessagingContainer.js

// ...
export default class MessagingContainer extends React.Component {
static propTypes = {
// From `KeyboardState`
containerHeight: PropTypes.number.isRequired,
contentHeight: PropTypes.number.isRequired,
keyboardHeight: PropTypes.number.isRequired,
keyboardVisible: PropTypes.bool.isRequired,
keyboardWillShow: PropTypes.bool.isRequired,
keyboardWillHide: PropTypes.bool.isRequired,
keyboardAnimationDuration: PropTypes.number.isRequired,
// Managing the IME type
inputMethod: PropTypes.oneOf(Object.values(INPUT_METHOD)).isRequired,
onChangeInputMethod: PropTypes.func,
// Rendering content
children: PropTypes.node,
renderInputMethodEditor: PropTypes.func.isRequired,

Core APIs, Part 2

300

};
static defaultProps = {
children: null,
onChangeInputMethod: () => {},
};
// ...
}

Now let’s use componentWillReceiveProps to handle switching the inputMethod. When the keyboard transitions from hidden to visible, we want to set the inputMethod to INPUT_METHOD.KEYBOARD.
When the keyboard transitions from visible to hidden, we want to set the inputMethod to INPUT_METHOD.NONE… unless we’re currently displaying the image picker (the keyboard should always be
hidden when we display the image picker, so we can ignore this transition).
messaging/components/MessagingContainer.js

// ...
componentWillReceiveProps(nextProps) {
const { onChangeInputMethod } = this.props;
if (!this.props.keyboardVisible && nextProps.keyboardVisible) {
// Keyboard shown
onChangeInputMethod(INPUT_METHOD.KEYBOARD);
} else if (
// Keyboard hidden
this.props.keyboardVisible &&
!nextProps.keyboardVisible &&
this.props.inputMethod !== INPUT_METHOD.CUSTOM
) {
onChangeInputMethod(INPUT_METHOD.NONE);
}
// ... more to come!
}
// ...

Since inputMethod will be stored in the state of the parent, we’ll call onChangeInputMethod
and let the parent pass this prop back down. We could store inputMethod in the state of

Core APIs, Part 2

301

MessagingContainer, but since the parent needs to access this value, it’s best that the parent stores

it.

LayoutAnimation
We’re going to use LayoutAnimation to handle automatically transitioning between the various
states of this component. LayoutAnimation is still considered experimental, and it’s more common
to use the other animated API, Animated. However, LayoutAnimation is the only way we can match
the exact animation of the keyboard. It’s used internally by the built-in KeyboardAvoidingView
component, so it’s safe for us to use despite being considered experimental.
Currently LayoutAnimation is disabled by default on Android, so we need to enable it by calling
UIManager.setLayoutAnimationEnabledExperimental(true). We can enable it anywhere in the app,
but let’s do it at the top of MessagingContainer.js, since that’s the file we use it in:
messaging/components/MessagingContainer.js

if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}

The UIManager object contains a variety of APIs for getting access to native UI elements
for measuring, but we won’t use it for anything else here.
LayoutAnimation automatically handles animating elements that should change size or appear/disappear between calls to render. We call LayoutAnimation.create to define an animation configuration, and then LayoutAnimation.configureNext to enqueue the animation to run the next time
render is called.

The LayoutAnimation.create API takes three parameters:
• duration - The duration of the animation
• easing - The curve of the animation. We choose from a predefined set of curves: spring, linear,
easeInEaseOut, easeIn, easeOut, keyboard. The keyboard curve is the key to matching the
keyboard’s animation curve – although it only exists on iOS.
• creationProp - The style to animate when a new element is added: opacity or scaleXY.
In our case, we want to call this every time the component will re-render, so componentWillReceiveProps
is the best place.

Core APIs, Part 2

302

messaging/components/MessagingContainer.js

// ...
componentWillReceiveProps(nextProps) {
// ... from before!
const { keyboardAnimationDuration } = nextProps;
const animation = LayoutAnimation.create(
keyboardAnimationDuration,
Platform.OS === 'android' ? LayoutAnimation.Types.easeInEaseOut : LayoutAnimatio\
n.Types.keyboard,
LayoutAnimation.Properties.opacity,
);
LayoutAnimation.configureNext(animation);
}
// ...
LayoutAnimation applies to the entire component hierarchy, not just the component we call it from,

so this will actually animate every component in our app. It may be a better idea to selectively choose
when to animate based on the exact props which have changed, but for simplicity, let’s assume we
always want to animate.

Handling the back button
We should add one last bit of logic in MessagingContainer.js to handle the hardware back button
on Android. When the CUSTOM IME is active, we want the back button to dismiss the IME, just like it
would for the device keyboard. We’ll use BackHandler for this. When the back button is pressed, if
the CUSTOM IME is active, we’ll call onChangeInputMethod(INPUT_METHOD.NONE) to notify the parent.
messaging/components/MessagingContainer.js

// ...
componentDidMount() {
this.subscription = BackHandler.addEventListener('hardwareBackPress', () => {
const { onChangeInputMethod, inputMethod } = this.props;
if (inputMethod === INPUT_METHOD.CUSTOM) {
onChangeInputMethod(INPUT_METHOD.NONE);
return true;

Core APIs, Part 2

303

}
return false;
});
}
componentWillUnmount() {
this.subscription.remove();
}
// ...

Rendering the MessagingContainer
Now let’s render this thing! We’ll render an outer View which contains the message list and the toolbar (via children), and an inner View which renders the image picker (via renderInputMethodEditor).
The conditional logic is pretty complex, so let’s take a look at it in-line with the code.
messaging/components/MessagingContainer.js

// ...
render() {
const {
children,
renderInputMethodEditor,
inputMethod,
containerHeight,
contentHeight,
keyboardHeight,
keyboardWillShow,
keyboardWillHide,
} = this.props;
// For our outer `View`, we want to choose between rendering at full
// height (`containerHeight`) or only the height above the keyboard
// (`contentHeight`). If the keyboard is currently appearing
// (`keyboardWillShow` is `true`) or if it's fully visible
// (`inputMethod === INPUT_METHOD.KEYBOARD`), we should use
// `contentHeight`.
const useContentHeight =
keyboardWillShow || inputMethod === INPUT_METHOD.KEYBOARD;

Core APIs, Part 2

304

const containerStyle = {
height: useContentHeight ? contentHeight : containerHeight,
};
// We want to render our custom input when the user has pressed the camera
// button (`inputMethod === INPUT_METHOD.CUSTOM`), so long as the keyboard
// isn't currently appearing (which would mean the input field has received
// focus, but we haven't updated the `inputMethod` yet).
const showCustomInput =
inputMethod === INPUT_METHOD.CUSTOM && !keyboardWillShow;
// If `keyboardHeight` is `0`, this means a hardware keyboard is connected
// to the device. We still want to show our custom image picker when a
// hardware keyboard is connected, so let's set `keyboardHeight` to `250`
// in this case.
const inputStyle = {
height: showCustomInput ? keyboardHeight || 250 : 0,
};
return (

{children}
{renderInputMethodEditor()}

);
}
// ...

In order for the toolbar to sit above the home indicator on the iPhone X, we’ll need to adjust the
space below the toolbar as the keyboard transitions up and down.
Supporting the iPhone X
You may skip this section if you’re not testing with an iPhone X.

In the “Core Components” chapter, we used the SafeAreaView to support the iPhone X. This won’t
work here, since we want to animate the space below the toolbar (to avoid a jerk when the space
changes).

Core APIs, Part 2

305

We’ll install the npm library react-native-iphone-x-helper65 to help us determine if the device is
an iPhone X.
In your terminal, install the library with:
yarn add react-native-iphone-x-helper@1.0.1

React Native doesn’t currently provide a way to determine if the device is an iPhone X. This
library simply checks the device’s dimensions. Hopefully in the future something better will
be provided out-of-the-box for accessing the safe area insets directly.

After this finishes, import the isIphoneX utility function at the top of the file:
messaging/components/MessagingContainer.js

import { isIphoneX } from 'react-native-iphone-x-helper';

Now we can update the render method above to include extra space below the toolbar:
messaging/components/MessagingContainer.js

// ...
render() {
// ...
// The keyboard is hidden and not transitioning up
const keyboardIsHidden =
inputMethod === INPUT_METHOD.NONE && !keyboardWillShow;
// The keyboard is visible and transitioning down
const keyboardIsHiding =
inputMethod === INPUT_METHOD.KEYBOARD && keyboardWillHide;
const inputStyle = {
height: showCustomInput ? keyboardHeight || 250 : 0,
// Show extra space if the device is an iPhone X the keyboard is not visible
marginTop: isIphoneX() && (keyboardIsHidden || keyboardIsHiding) ? 24 : 0,
};
// ...
65 https://www.npmjs.com/package/react-native-iphone-x-helper

Core APIs, Part 2

306

}
// ...

Whew, we made it. Save MessagingContainer.js. Now we just need to render MessagingContainer
from App.

Rendering MessagingContainer in App
Head back to App.js and import the components we’ve just created:
messaging/App.js

// ...
import KeyboardState from './components/KeyboardState';
import MeasureLayout from './components/MeasureLayout';
import MessagingContainer, { INPUT_METHOD } from './components/MessagingContainer';
// ...

Let’s include the inputMethod in the state of App, and handle changes to it.
messaging/App.js

// ...
export default class App extends React.Component {
state = {
// ...
inputMethod: INPUT_METHOD.NONE,
};
// ...
handleChangeInputMethod = (inputMethod) => {
this.setState({ inputMethod });
};
handlePressToolbarCamera = () => {
this.setState({
isInputFocused: false,
inputMethod: INPUT_METHOD.CUSTOM,
});

Core APIs, Part 2

307

};
// ...
}
// ...

Lastly, let’s use MeasureLayout, KeyboardState, and MessagingContainer to render the UI components we’ve already written. We’ll rearrange this.renderMessageList,this.renderToolbar, and
this.renderInputMethodEditor so that they render within MessagingContainer.
We can update the render method of App to look like this:
// ...
render() {
const { inputMethod } = this.state;
return (



{layout => (

{keyboardInfo => (

{this.renderMessageList()}
{this.renderToolbar()}

)}

)}

{this.renderFullscreenImage()}

);
}

Core APIs, Part 2

308

// ...

Using the children function prop pattern, we can pretty clearly visualize the flow of data downward
into MessagingContainer.
Note that since keyboardInfo contains many properties, it’s easiest to pass them all into MessagingContainer
at once with the object spread syntax ...keyboardInfo. If we prefer, we could also assign each
property individually, e.g. keyboardHeight={keyboardInfo.keyboardHeight}.
Save App.js and test it out! You should see the same components as before, but now they animate
smoothly to avoid the keyboard as it appears and disappears.
In the default state, our app should look like this:

Tapping the input field should pop open the keyboard, and smoothly transition the rest of the UI:

Core APIs, Part 2

Tapping the camera icon should transition to the image picker:

309

Core APIs, Part 2

310

We’re Done!
We’ve built a messaging app UI complete with text messages, images, and maps. We notify the
user of connectivity issues. We display a pixel-perfect infinite scrolling grid of photos. We smoothly
animate the UI as new messages are added and removed (try it if you haven’t! the LayoutAnimation
takes care of this automatically). We handle the keyboard gracefully on both platforms.

Navigation
In the “Core Components” chapter, we explored how different parts of the app (the image feed and
user comments) can be represented as separate screens - components that take up the entire device
screen. When building screens, handling how the user navigates between them is a primary concern.
Navigation is a major piece of any mobile application with multiple screens. With a navigation
system in place, a user can access any part of an application. It also allows us to structure and
separate how data is handled in the app.
Handling navigation in a mobile application is fundamentally different from a website. For a website,
the state of a user’s location is usually kept in the browser’s URL. Although the browser maintains
a history of pages visited in order to allow the user to move back and forth, the browser only stores
page URLs and is otherwise stateless. On mobile, the entire history stack is maintained and can be
accessed.
On mobile, we have more control and flexibility over history management. We can keep a history
stack that includes details of each route including parameters and part of the application state.
Further, mobile navigation presents its own set of challenges. One of the biggest is the reduced real
estate of the user’s device screen compared to a desktop or laptop computer. We need to make sure
there are easily visible and identifiable navigation components that will allow the user to move to
another part of the application when pressed. Including a complex navigation flow comes with the
cost of a larger number of navigation components (such as menu options). For this reason, most
mobile apps tend to have a small and focused number of screens that a user can easily navigate to
and understand.

Navigation in React Native
This section will explore the landscape of navigation in React Native in some detail. If you
would like to jump straight to building our sample application, feel free to skip this section
and return to it later.

One of the primary navigation patterns in a mobile app is a stack-based pattern. In this pattern, only
one screen can be seen by the user at any given time. Navigating involves pushing the new screen
onto the navigation stack. We’ll explore stack-based navigation in more detail later in the chapter.
For now, it is important to realize that this pattern, among others, uses different native components
for iOS and Android. For example, building a stack-based navigation flow between screens can be
done using UINavigationController66 for iOS and connecting Activities67 for Android.
66 https://developer.apple.com/documentation/uikit/uinavigationcontroller
67 https://developer.android.com/guide/components/activities/index.html

Navigation

312

There are two primary approaches to navigation in React. We can either include actual native
iOS/Android navigational elements or use JavaScript to create the required animations and components that we need.

Native navigation
The first way we can add navigation is to use native iOS/Android navigational components.
In an iOS application, views are used to build the UI and display content to the user. A view controller
(or the UIViewController class) is used to control a set of views and allows us to connect our UI
with our application data. By including multiple view controllers in our app, we can build different
screens as well as transition between them.
A navigation controller (UINavigationController ) simplifies the process of navigating between
screens by allowing us to pass in a stack of UIViewControllers. It will take care of including a
header navigation bar at the top of our device with a back button that allows us to pop the current
view controller off of the current stack. With this, it maintains the hierarchy of all the screens within
the stack.

Example of a navigation controller (from Apple Developer Documentation - UINavigationController)

In Android, activities are used to create single screens to define our UI. We can use tasks in order
to define a stack of activities known as the back stack. The startActivity method can be used to
start a new activity. When this happens, the activity is pushed onto the activity stack. In order to
return to the previous screen, the physical back button on every Android device can be pressed in
order to run the finish method on the activity. This closes the current activity, pops it off the stack
and returns the user back to the previous activity.

313

Navigation

Android Back Stack (from Android Developers Documentation - Tasks and Back Stack)

In React Native, all of our component code executes on a JavaScript thread. These components then
bridge to a separate main thread responsible for rendering native iOS and Android views.
In the first chapter, we briefly mentioned how we can eject from Expo if we need to include any
native dependencies ourselves. This includes any native iOS or Android code we wish to write
ourselves or third-party libraries that provide a React Native API which bridges to a specific native
module. We can use this to include native navigation in our application. We can create native
modules around platform-specific navigation components (such as UINavigationController and
Activity) and bridge them ourselves in a React Native app.
We’ll explore bridging native APIs in much more detail in the “Native Modules” chapter.

Pros
The primary benefit of this approach is a smoother navigation experience for the user. This is because
purely native iOS/Android navigation APIs can be used with all of our navigation happening within
the native thread. This approach works well when including React Native in an existing native iOS
or Android application. Using the same navigation components and transitions throughout the app
means that different screens in the app will feel consistent regardless of whether they’re written
natively or with React Native.
Additionally, if an operating system update modifies the style or functionality of navigation
components, you won’t have to wait for the same modifications to be made in your JavaScriptbased navigation library.
Cons
One of the issues with this navigation approach is that it usually involves more work. This is
because we need to ensure navigation works for both iOS and Android, using their respective native
navigational components.

314

Navigation

Moreover, we will also have to eject from Expo and take care of linking and bridging any
native modules ourselves. This means we cannot build an application with native iOS navigation
components if we do not own a Mac computer.
Another potential problem with this solution is that it can be significantly harder to modify or create
new navigation patterns. In order to customize how navigation is performed, we would have to dive
in to the native code and understand how the underlying navigation APIs work before being able
to change them.

Navigation with JavaScript
The second approach to adding navigation to a React Native app is to use JavaScript to create
components and navigation patterns that look and feel like their native counterparts. This is done
solely using React Native built-in components and the Animated API for animations.
We can explain how this works by using stack navigation as an example again. As we mentioned
earlier, this pattern allows us to move between screens by pushing a second screen on top of the
previous one. We usually see this happen by seeing the second screen slide in from either the right
or bottom edge of the device screen or fading in from the bottom. When we attempt to navigate
backwards, the current screen slides back out in the opposite direction or fades out from the top.
With the Animated API, we can use animated versions of some built-in components such as View as
well as create our own. We can create stack based navigation (as well as other navigation patterns)
by nesting our screen components within an Animated component. We can then have our screens
slide (or fade) in and out of our device when we need to allow the user to navigate throughout our
application. We’ll have to maintain the hierarchy of screens entirely in JavaScript ourselves.
Pros
One of the advantages of using JavaScript-based navigation is that it can be simpler to build the
components and animation mechanisms that can be used in both platforms instead of trying to
create a bridge to all of the core native iOS/Android APIs that we would need. This also gives us
more control and flexibility to customize specific navigation features instead of relying on what’s
available in the native platforms. We can debug any issues we experience with navigation that is
purely JavaScript-based without diving in to native code.
Most of the work during an animation using the React Native Animated API is on the JavaScript
thread. This means that every frame needs to go over the bridge to the native thread to update the
views during a transition. Fortunately, we have the option to use the API’s native driver option to
render natively driven animations. These animations are performed with animation calculations
happening on the native thread. By building navigation with this, navigation animations will
perform smoothly.
We’ll explore the built-in Animated API in greater detail in the next chapter.

Navigation

315

Another benefit of keeping all of our navigation elements within the JavaScript thread means that
we can take advantage of services such as CodePush68 to allow us to dynamically update the
application’s JavaScript code (which includes our navigation) without rolling out a new build to
our users.
Cons
There are also disadvantages with this approach. Firstly, the app can never feel exactly like a native
application in terms of navigation. As much as we can try to mimic how navigation components
and animations look like in the native layer, there may always be slight discrepancies. This can be
a bigger problem if we happen to be including React Native components into an existing native iOS
or Android application. Building transitions between screens built natively and screens built with
React Native can be a challenge if we’re using only JavaScript for our navigation.
Another potential concern with JavaScript-based navigation is slower updates in relation to the
underlying platform’s operating system. Updates to the iOS or Android version may bring changes
to the native navigational views and components. We’ll need to make sure the views and components
built with JavaScript are also updated in order to better match how they are represented natively.

Third-party libraries
In React Native, we have the option of setting up navigation by creating our own native modules
or by building our own JavaScript-based implementation. There are also a number of communitysupported libraries that we can use for either of these approaches:
• React Native Navigation69 by Wix engineering and Native Navigation70 by Airbnb are both
navigation libraries that provide access to native iOS/Android navigation components using a
React Native API.
• React Navigation71 and React Router72 are two popular third-party JavaScript navigation
libraries.
Using one of these libraries can make things significantly easier than building a navigation pattern
entirely from scratch. Moreover, all of these community-built libraries are continuously maintained
with updates included in each new release.

Navigation alternatives
Not all mobile applications need to have a complete navigation architecture. Examples include an
app that only has a few screens or does not even need any navigation in the first place (such as a
68 https://github.com/Microsoft/react-native-code-push
69 https://wix.github.io/react-native-navigation/#/
70 http://airbnb.io/native-navigation/
71 https://facebook.github.io/react-native/docs/navigation.html#react-navigation
72 https://reacttraining.com/react-router/native/guides/philosophy

316

Navigation

single-screen game). However, most applications with more than a few screens will usually need
some form of navigation to allow the user to move between them.
There is no single correct solution for applications that require navigation. Different mobile apps will
always have different features and complexities. For example, it may be easier to use a JavaScript
implementation in a brand new and relatively simple application without complex navigation
requirements. However, we might find it easier to use a native solution if we plan on rolling React
Native components into a native application. We should always weigh the benefits and challenges
of each solution before deciding which approach to take.
Deprecated solutions
Navigation is a core tenet of any native application. Just like other built-in components (such as View
and Text), React Native also used to provide a number of different built-in navigation APIs. Here
are a few examples:
• NavigatorIOSa provides an API to access the UINavigationController component for iOS to
build a screen navigation stack. It is not currently maintained and cannot be used for Android.
• Navigator is a JavaScript navigation implementation that was included into React Native when
it first launched. Expo built ExNavigator on top of this API with the aim of providing more.
features. However, it did not provide a complete navigation solution and was deprecated soon
after.
• NavigationExperimental is another JavaScript implementation and aimed to solve the problems noticed in Navigator. ExNavigation was built by the Expo team to act as a wrapper
around NavigationExperimental. It is also now deprecated.
It is important to note that all of these APIs are either not maintained or are deprecated. It is
recommended to use one of the newer community-built navigation libraries instead.
Since navigation is an important part of many mobile applications, all of these efforts were done in
order to provide a simple React Native API that can be imported and used directly in a component.
However, navigation is a lot more complex than many other built-in components. It is not easy to
provide a simple navigation API that can solve all navigation concerns in any application. For this
reason, a number of different open-source alternatives were created by the community. The efforts
from Navigator, NavigationExperimental, and the community-built ex-navigation were combined
to form the community-built React Navigation library.
a https://facebook.github.io/react-native/docs/navigatorios.html

In this chapter
We covered the differences between native and JavaScript navigation implementations as well
as some of their advantages and disadvantages. For each approach, we also discussed how using
an open-source library that is continuously maintained can make things easier than building a

317

Navigation

navigation architecture from scratch.
Co-authored by the Facebook team and the open-source community, React Navigation73 is the
recommended option in the React Native documentation. For this reason, we’ll use React Navigation,
a JavaScript-based implementation, in this chapter to handle navigation in our sample application.

Contact List
In this chapter, we’re going to build a contact list application that allows a user to view contact
information across several screens. We’ll begin by building our first screen, Contacts, that shows a
list of contact information fetched from a remote API.

Contacts

Then we’ll explore how we can allow the user to navigate to a separate Profile screen for each
specific contact.
73 https://facebook.github.io/react-native/docs/navigation.html#react-navigation

318

Navigation

Profile

We’ll then include another top level screen, Favorites, to show favorited contacts.

319

Navigation

Favorites

Then we’ll build a User screen and tie together our top level screens using a tab navigation
component.

320

Navigation

User

The last screen we’ll create will be an options screen that the user can navigate to through the user
screen.

321

Navigation

Options

By building out each screen and connecting them, we’ll get a better understanding of how a
navigation pattern such as tabs can be coupled with stack navigation. While doing so, we’ll also look
into creating a small state container that controls the entire state of our app and can be accessed in
any of our screens.
By the time we’re finished with this chapter, we will have covered a number of different navigation
patterns and show how they can fit together. We’ll also be touching on a number of core concepts
we’ve already covered in the previous chapters.

Previewing the app
Before we begin, to try the completed app on your device:
• On Android, you can scan this QR code with the Expo app:

322

Navigation

QR Code

• On iOS, you can navigate to the contact-list/ directory within the sample code folder and
either preview it on the iOS simulator or send the link of the project URL to your device as we
explained in the first chapter.
Spend a little time navigating between the different screens to get a feel for all the functionality.

Starting the project
Just as we did in the previous chapters, let’s create a new app with the following command:
create-react-native-app contact-list --scripts-version 1.14.0

Once this finishes, navigate into the contact-list directory and start the app.

Navigation

323

Like you did in previous chapters, copy over the contact-list/utils directory from the sample
code into your own project. The utils/ directory contains the following:
• A few utility methods
• Methods that return results from our external API, Random User Generator74
• A colors object with a number of different colors
Copy over the contact-list/components directory as well. These components are the low level
presentational components that don’t manage any state of their own. They are used in the app to
display UI elements in all of our screens. Here’s a brief overview of each of the components:
• ContactListItem will be used for each contact’s list item in the Contacts screen.
• ContactThumbnail renders a thumbnail for the contact avatar that can fire an action when
pressed. It can also show the user’s name and phone number underneath based on optional
props. This component will be used to render a list of user avatars in the Favorites screen as
well as show the user thumbnail in the Profile and User screens.
• DetailListItem shows a list item with a title, subtitle, and an optional icon. This component
will be used to show contact details in the Profile screen as well as mocked links in the Options
screen.
74 https://randomuser.me/

Navigation

324

Feel free to dive in and take a closer look at any of the files within utils/ and components/ to get a
better idea of how they work.

Container and Presentational components
Before we dive in to building our application, let’s take a little time to further understand how
we separate our screen and component logic. In all of our previous chapters, we explored building
custom components to create higher-level abstractions over built-in components (such as View, Text,
etc…). We can think of screens in the same way. Just like any other component, screens wrap over
lower level components. The difference here is that we can build our screen components to take up
the entire device screen and allow the user to navigate between them.
We briefly explored this pattern in the “Core Components” chapter where we built a Feed and
Comments screen for our Instagram clone app. While doing so, we managed all of our remote data
fetching within the Feed screen and the rest of our lower level components only received this
information via props. This further ties in to the pattern we’ve seen in each of the applications
we’ve built in this book so far: the concept of container components that take care of data fetching
and state management and presentational components that take in data and provide the markup
and styling in our application. We can closely follow this logic by separating how we build screens
and components in an application.
Although the Feed screen was responsible for data fetching in our Instagram clone app, we still had
some state managed in our root App component. For a relatively large application with a significant
number of screens, managing data in a single component like App may not be the most maintainable
way to handle state. For this reason, third-party state container libraries are commonly used. Instead
of using a specific library and trying to understand its APIs, we’ll handle data in our application by
writing a small custom state container. The same pattern will apply to any complex application with
a central state container regardless of which library is used.

Contacts
The first screen we’ll build is the main contacts screen which will also serve as the starting point
of our application. Create a screens/ directory and add a Contacts.js file. As we mentioned in
the “Core Components” chapter, a more complex application might be structured with directories
nested within screens/ for better categorizing of files. Since this application only consists of five
screens, we’ll add them all to the screens/ directory.
We’ll begin by defining our imports in the file:

Navigation

325

contact-list/1/screens/Contacts.js

import React from 'react';
import {
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator,
} from 'react-native';
import ContactListItem from '../components/ContactListItem';
import { fetchContacts } from '../utils/api';

We’ve imported a few necessary built-in components including FlatList and ActivityIndicator
as well as our custom ContactListItem component responsible for displaying each of our contacts
items in the list. Aside from components, we also import the fetchContacts method in order to
retrieve our list of contacts and our colors object from utils.
Now let’s begin creating our class component:
contact-list/1/screens/Contacts.js

export default class Contacts extends React.Component {
state = {
contacts: [],
loading: true,
error: false,
};
async componentDidMount() {
try {
const contacts = await fetchContacts();
this.setState({
contacts,
loading: false,
error: false,
});
} catch (e) {
this.setState({
loading: false,
error: true,
});

Navigation

326

}
}

We’ve set up local component state that includes a contacts array and loading/error attributes.
In here, we’ve set our initial loading property to true because we fire our API call as soon as our
component mounts. We then update this to false as soon as our request finishes successfully.
Let’s now build the UI that gets rendered on the screen:
contact-list/1/screens/Contacts.js

renderContact = ({ item }) => {
const { name, avatar, phone } = item;
return ;
};
render() {
const { loading, contacts, error } = this.state;
const contactsSorted = contacts.sort((a, b) =>
a.name.localeCompare(b.name));
return (

{loading && }
{error && Error...}
{!loading &&
!error && (

)}

);
}

In the component render method, we sort our contacts alphabetically and show a loading indicator
if state.loading is true, an error message if state.error is true, or a list of our contacts using
FlatList. For each item in the list, we use a renderContact helper method that passes down the
contact name, avatar and phone as props to ContactListItem.

Navigation

327

We’ll also need to create our list’s keyExtractor method which we can write under our imports at
the top of the file:
contact-list/1/screens/Contacts.js

import { fetchContacts } from '../utils/api';
const keyExtractor = ({ phone }) => phone;
export default class Contacts extends React.Component {

The last thing we’ll need to do is set up the styles for this component. Since our existing
presentational component ContactListItem takes care of most of our styling, we’ll just set up styles
for the container View component:
contact-list/1/screens/Contacts.js

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

Try it out
To quickly take a look at how this screen renders, we can temporarily place this component within
App:
contact-list/App.js

import React from 'react';
import Contacts from './screens/Contacts';
export default function App() {
return ;
}

Now we can see our Contacts screen if we run the app:

328

Navigation

Contacts

You may notice the top and bottom of our list touches the edges of our device screen. This will be
fixed once we introduce our header and tab navigation components to the app. Pressing any of the
contacts does not do anything just yet. We’ll explore how we can navigate to a specific contact’s
profile screen in a bit.
You may also see a warning about a missing onPress prop which is required in the ContactListItem
component. We’ll include it once we begin adding navigation to our application.

Profile
Let’s move on to building our second screen, Profile, which shows details about a specific contact.
Create a Profile.js file within the same screens directory. Again, we’ll begin with our imports:

Navigation

329

contact-list/1/screens/Profile.js

import React from 'react';
import { StyleSheet, View } from 'react-native';
import ContactThumbnail from '../components/ContactThumbnail';
import DetailListItem from '../components/DetailListItem';
import { fetchRandomContact } from '../utils/api';
import colors from '../utils/colors';

We’ve included the ContactThumbnail and DetailListItem presentational components that we’ll
need for this screen as well as the colors object we’ll use for some styling.
We’ve also included fetchRandomContact to obtain a random contact’s information. This is temporary in order to render this screen for the first time, but will be removed once we have navigation
in place and the contact ID is passed from the previous screen to the Profile screen.
We can build our class component as follows:
contact-list/1/screens/Profile.js

export default class Profile extends React.Component {
state = {
contact: {},
};
async componentDidMount() {
const contact = await fetchRandomContact();
this.setState({
contact,
});
}
render() {
const {
avatar, name, email, phone, cell,
} = this.state.contact;
return (




Navigation

330








);
}
}

We’ve defined a contact object as our only attribute in our component state. Our componentDidMount
method fires an API call to get a random contact. Again, this is temporary until we’ve included our
navigation library. This is because we’ll eventually pass the contact’s information from the Contacts
screen.
We have not included any loading or error attributes for this same reason. This is because once
navigation is in place, this screen will only be accessible through another screen by pressing on a
contact list item or thumbnail. This means there will be no loading or potential errors from data
fetching happening at this point.
Although this might usually be the case when building screens that are only accessible through
other screens in a stack, there are scenarios where we may need to fetch data in nested screens. A
good example is deep linking which allows a user to navigate to a certain part of the app through
another app or a web browser using a specific link. We’ll explore this topic later in this chapter.
The render method is relatively straightforward. We show the user thumbnail for the top half of the
screen using ContactThumbnail as our component (which accepts the user’s avatar, name, and phone
as props). For the bottom half of the screen, we’re displaying a few DetailListItem components to
show the user’s email, work, and cell numbers.
We can now create styling for the View container components we’re using for layout at the end of
the file:
contact-list/1/screens/Profile.js

const styles = StyleSheet.create({
container: {
flex: 1,
},
avatarSection: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.blue,
},

Navigation

detailsSection: {
flex: 1,
backgroundColor: 'white',
},
});

Try it out
Once again, let’s render our screen to see if everything is working well:
contact-list/App.js

import React from 'react';
import Profile from './screens/Profile';
export default function App() {
return ;
}

Running the app should show the Profile screen for a random contact:

331

332

Navigation

Profile

React Navigation
Now that we have our first two screens in place, let’s start adding navigation to our app! As we
mentioned earlier in the chapter, there are a number of different open-source navigation libraries
available. We’ll be using React Navigation75 for this application. Since it’s purely a JavaScript
implementation, we don’t have to worry about linking iOS and Android dependencies. We can
install it to our app by using yarn:
yarn add react-navigation@1.5.11

Specify version 1.5.11 as above so that the version in your application matches the version we use
here.

Stack navigation
We briefly described how a stack navigator works earlier in this chapter. This pattern allows a user
to navigate from one screen to another by pushing the new screen to the top of the stack. The user
can also pop the current screen off the stack in order to return to the previous screen.
75 https://facebook.github.io/react-native/docs/navigation.html#react-navigation

Navigation

333

In both iOS and Android, a back button at the top of the navigation bar is how a user usually
navigates back to a previous screen by removing the current screen off the stack. On Android devices,
there is also a physical or soft key back button at the bottom of the device screen that also allows
you to go back on any application.
With this navigation flow, only one screen is visible at any given time. We can think of the entire
navigation stack as an ordered array of screens, with the last element being the screen that is
currently visible and the first element being the root screen (or the screen that is visible when loading
the app for the first time).
Let’s begin by connecting our first two screens as a single stack. We’ll define all of our navigation
logic in a separate routes.js file at the root of our entire app.
Create the file and add the following code:
contact-list/2/routes.js

import { StackNavigator } from 'react-navigation';
import Contacts from './screens/Contacts';
import Profile from './screens/Profile';
export default StackNavigator({
Contacts: {
screen: Contacts,
},
Profile: {
screen: Profile,
},
});

We pass in our route configuration as an argument to StackNavigator. The object maps route names
to their configuration. We have two routes defined above, Contacts and Profile.
For each route, we define a configuration object with just one property: screen. This is the
component we wish to render at that specific route. We’ll explore more configuration options in
a bit.
We can also pass a second argument to StackNavigator, our stack navigator configurations. We’ll
also explore this a little later in this section.
In React Navigation, every navigator including StackNavigator creates a higher-order component
that wraps over each of the screen components defined within its route configurations. It enhances
each of its components by creating a newer component with a navigation prop.

334

Navigation

Higher-Order Components
In short, higher-order components are functions that take in an existing component and
return a new component with added functionality. They’re useful for minimizing code
duplication by containing common logic in a single component that can be shared among
multiple components. They’re also useful for libraries like React Navigation.
Internally, StackNavigator() generates a higher-order component that provides each of
our screens with a navigation prop. This prop serves as the interface between our screen
components and the React Navigation library.
For more information on higher-order components, refer to its section in the Appendix.

The navigation prop provides us with the following:
• navigate: Method to allow us to navigate between screens. With a StackNavigator, this
method pushes the new screen on top of the current stack.
• state: Object that returns the name and identifier of the current route as well as its parameters.
• setParams: Method to change the current screen’s parameters.
• goBack: Method that allows us to navigate to a previous screen. For StackNavigator, this pops
the current screen (or number of screens) until the specified screen is reached within the stack.
The React Navigation documentation76 contains more detail about the navigation prop.
Let’s now modify our App.js file to render our navigator instead of a single component:
contact-list/2/App.js
1

import React from 'react';

2
3

import AppNavigator from './routes';

4
5
6
7

export default function App() {
return ;
}

Now that we’ve set up the first StackNavigator of our application, we’ll need to use our navigation
prop to allow the user to navigate from the Contacts screen to the Profile screen. We know that
the ContactListItem component contains an onPress prop that fires the action passed to it. Let’s
modify how we render this component within our Contacts screen:

76 https://reactnavigation.org/docs/navigation-prop.html

335

Navigation

contact-list/2/screens/Contacts.js

renderContact = ({ item }) => {
const { navigation: { navigate } } = this.props;
const { name, avatar, phone } = item;
return (
 navigate('Profile')}
/>
);
};

Try running the app and you’ll notice a header navigation bar at the top of the screen. In addition
to supplying the navigation prop, the StackNavigator HOC also renders a header above the screen
components it wraps.

Contacts

336

Navigation

Pressing any contact will navigate to the profile screen. The default behaviour for iOS is an animation
that slides the new screen from the right. For Android, the newer screen fades in from the bottom.

Profile

Pressing the back button also pops the profile screen of the stack and returns us to the contacts
screen.
In your terminal, you may be seeing a warning similar to the following:
Warning: isMounted(...) is deprecated in plain JavaScript React classes...

This warning shows when using our current version of React Navigation (1.5.11) with our version
of React Native (0.55.2). There is currently an open GitHub issuea where this should be resolved in
a future release.
a https://github.com/react-navigation/react-navigation/issues/3956

Although our stack navigation pattern works, we have two problems:
• Recall that we’re using the fetchRandomContact() method to obtain a random contact. This
means that pressing a specific contact doesn’t actually load their information in the Profile
screen.

Navigation

337

• The header navigation bar doesn’t currently show anything. We should attempt to show the
current screen name so the user knows which screen they’re on.

Navigation parameters
When building multiple screens in a mobile application, it is common to have screens that depend
on some particular data in order to display the correct information. A good example is the Profile
screen in this application. Everytime a user presses a contact on the Contacts screen, we expect to
see that specific contact’s information on the next screen.
The secondary screen here is not a child of the previous screen, but it still relies on a piece of data.
In these scenarios, we need to be able to pass this data as part of the transition in our navigation
flow. React Navigation lets us attach navigation parameters using the navigate method.
We previously mentioned that the navigation prop allows us to change parameters for a screen
using its setParams method. We can similarly pass parameters to another screen. Let’s take a look
at how we set this up for navigating from the Contacts screen to Profile:
contact-list/3/screens/Contacts.js

renderContact = ({ item }) => {
const { navigation: { navigate } } = this.props;
const {
id, name, avatar, phone,
} = item;
return (
 navigate('Profile', { contact: item })}
/>
);
};

In the second argument of the navigate method, we pass a single object for our parameters that
contains a contact key with the value being the actual contact item. This means that every time we
press a contact on the Contacts screen, the user is navigated to the Profile screen with the actual
contact being passed as a parameter.
With this, we can simplify our Profile screen component and not load a specific contact every time
the screen is mounted:
Since we are now receiving a contact object through navigation props, we no longer need to fetch a
random contact using fetchRandomContact(). We can simplify our Profile screen and not fire any
API calls when our screen mounts:

Navigation

338

contact-list/3/screens/Profile.js
export default class Profile extends React.Component {

render() {
const { navigation: { state: { params } } } = this.props;
const { contact } = params;
const {
avatar, name, email, phone, cell,
} = contact;
return (










);
}
}

We’ve removed all local state from Profile and the component is now driven by props. We extract
the contact object from the navigation prop and use that to render the contact’s information. If
you try running the app now, navigating to a specific contact will show the correct information.
Although passing in the contact object as a navigation parameter works, we generally want to avoid
this pattern.
So far, we’ve explored how parents and children use props to communicate. If a parent performs a
state update, that update propagates through props down to its children. When the child receives
the updated props, the child re-renders.
What’s different here is that Profile is not a direct child of the Contacts screen. Instead Profile is
receiving this data through navigation parameters. Navigation parameters are set once, at the time
of navigation. So if the state for a contact changes, that state update will not be propagated through
navigation parameters.
Put another way, we’ve pushed a copy of a part of our state into navigation parameters. But we have
no means in place to update that copy when the state changes.
So, we should instead pass the id of a contact as a parameter. Then, Profile can look up the contact
in the list of all contacts. We’ll explore this improvement once we introduce a centralized location
for all our state later in this chapter.

Navigation

339

We’ve built the first two screens that make up our first navigator. We covered how to transition
between them by using the API supplied by the navigation prop.
Each screen will usually have its own unique set of features, and we’ll sometimes need to be able to
modify how our navigation-specific components are displayed. Next, we’ll explore how to configure
our navigation screen options before moving on to expanding the number of screens and navigation
patterns in our application.

Navigation screen options
With React Navigation, we can use the navigationOptions property to modify navigation settings for a particular screen or to modify settings for every screen within a navigator (such as
StackNavigator).
We’ll use this property to add a title to the navigation header at the top of both screens. Let’s add it
to each of our screens in route.js:
contact-list/3/routes.js

export default StackNavigator({
Contacts: {
screen: Contacts,
navigationOptions: {
title: 'Contacts',
},
},

We add our options using a navigationOptions object. We specify the title attribute to be Contacts.
We can run the application to confirm that this works.

340

Navigation

Contacts

Now let’s do the same for the Profile screen along with adding a few more details:
contact-list/3/routes.js

Profile: {
screen: Profile,
navigationOptions: ({ navigation: { state: { params } } }) => {
const { contact: { name } } = params;
return {
title: name.split(' ')[0],
headerTintColor: 'white',
headerStyle: {
backgroundColor: colors.blue,
},
};
},
},

Although we passed in an object to navigationOptions for the Contacts screen, we also can pass in
a function. Passing a function gives us access to the navigation prop. This is useful when we want

341

Navigation

our options to be derived from the navigation parameters. Here, we get the name of our contact and
use the split77 method to only render her or his first name as the title
We also modify the colors of our header by using headerTintColor, which allows us to change
the text color, and headerStyle to pass in an object of styles for the header. We change the
backgroundColor to blue.
If we try navigating to the Profile screen now, we’ll see our header styled appropriately.

Profile

Notice on iOS that the title on the left of the header defaults to the title of the previous
screen. We have control over this with the headerBackTitle property of navigation options
if we need to modify this.
For a full list of navigator configuration options provided by StackNavigator, refer to the
documentation78 .
77 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split
78 https://reactnavigation.org/docs/navigators/stack

342

Navigation

Default Navigation Options
Although we can specify all of our navigation options for each screen separately, it may
be useful to set default navigation options for multiple screens if they all share the same
configurations. We can do this by defining navigationOptions at the level of the navigator.
For example:
export default StackNavigator({
Contacts: {
screen: Contacts,
navigationOptions: {
headerStyle: {
backgroundColor: 'white',
},
},
},
Profile: {
screen: Profile,
},
}, {
navigationOptions: {
headerStyle: {
backgroundColor: colors.blue,
},
},
});

Screen-specific navigation options for the same configuration will overwrite those defined
for the navigator. In this example, all the screens within this navigator will have a default
background color of blue for their header components. The Contacts screen however will
have a white background color that overwrites the default setting.

Although we can add all of our screen-specific navigation options where we define our navigators
in routes.js, that file can quickly become quite large if we have a lot of screens and configuration
settings. We can instead define each screen’s navigation options inside each component.
Let’s begin with Contacts. We’ll remove the navigationOptions property from our routes.js file
and add it as a static class method to the Contacts component:

Navigation

343

contact-list/4/screens/Contacts.js

export default class Contacts extends React.Component {
static navigationOptions = {
title: 'Contacts',
};
state = {

This is the same technique we’ve used previously for propTypes and defaultProps. We can now do
the same thing for Profile:
contact-list/4/screens/Profile.js

export default class Profile extends React.Component {
static navigationOptions = ({ navigation: { state: { params } } }) => {
const { contact: { name } } = params;
return {
title: name.split(' ')[0],
headerTintColor: 'white',
headerStyle: {
backgroundColor: colors.blue,
},
};
};
render() {

Without the navigationOptions properties, we can clean up our routes.js file:
contact-list/4/routes.js
1

import { StackNavigator } from 'react-navigation';

2
3
4

import Contacts from './screens/Contacts';
import Profile from './screens/Profile';

5
6
7
8
9
10
11
12

export default StackNavigator(
{
Contacts: {
screen: Contacts,
},
Profile: {
screen: Profile,

344

Navigation
13
14
15
16
17
18

},
},
{
initialRouteName: 'Contacts',
},
);

The only new thing we’ve added here is the initialRouteName property. Without this property,
the first screen listed is the default screen. However, it’s better to explicitly define the initial route,
since we generally shouldn’t rely on the implicit order of keys within an object. Now, wherever this
navigator is loaded in the application, the first screen that shows will be the Contacts screen.
Like initialRouteName, React Navigation allows us to modify a number of different configuration properties for stack navigators besides navigationOptions. The documentation79
goes into more detail on each of them.

Tab navigation
A single stack navigator might suffice for a small mobile app with just two or three screens. However,
most applications have more than a few screens and only using stack navigation may not be the most
efficient way to navigate throughout the entire app. This is where another navigation paradigm, like
tabs, can be useful.
We can use tab navigation to allow the user to navigate to a number of different screens at the root
level. Tabs are suitable when a number of screens carry roughly equal importance.

Bottom navigation - (from Material Design documentation - Components)
79 https://reactnavigation.org/docs/stack-navigator.html#stacknavigatorconfig

Navigation

345

Favorites
Before we begin adding tab navigation components to our application, we’ll need to build a few more
screens. Let’s build the Favorites screen of our application first. We can create a Favorites.js file
within the screens directory and begin with its imports:
contact-list/5/screens/Favorites.js

import React from 'react';
import {
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator,
} from 'react-native';
import { fetchContacts } from '../utils/api';
import ContactThumbnail from '../components/ContactThumbnail';

As we briefly described earlier in this chapter, this screen will be responsible for showing a list of
favorited contacts.
The Favorites component will not be a child of the Contacts component. And because we’ll be using
tab navigation as opposed to stack navigation, we can’t pass contacts to favorites via a navigation
prop.
Therefore, we’ll use the fetchContacts() function to fetch this data directly from the API. We
usually want to avoid making numerous API calls to obtain the same data on every screen. Once
we include a state container later in the chapter, we’ll remove this.
Note that we’ve set up the fetchContacts API call to randomly “favorite” contacts by setting the
favorites boolean on the contact object to true. That way, we should always have some contacts
displayed on this screen.

Navigation

contact-list/5/screens/Favorites.js

export default class Favorites extends React.Component {
static navigationOptions = {
title: 'Favorites',
};
state = {
contacts: [],
loading: true,
error: false,
};
async componentDidMount() {
try {
const contacts = await fetchContacts();
this.setState({
contacts,
loading: false,
error: false,
});
} catch (e) {
this.setState({
loading: false,
error: true,
});
}
}

The screen’s render function:
contact-list/5/screens/Favorites.js

renderFavoriteThumbnail = ({ item }) => {
const { navigation: { navigate } } = this.props;
const { avatar } = item;
return (
 navigate('Profile', { contact: item })}
/>
);

346

Navigation

347

};
render() {
const { loading, contacts, error } = this.state;
const favorites = contacts.filter(contact => contact.favorite);
return (

{loading && }
{error && Error...}
{!loading &&
!error && (

)}

);
}

In render, we filter our list of contacts using a favorites flag. Using the same pattern we used in
Contacts, we show a loading indicator while the request is still being made, an error message if the
request fails, or the list of contacts.
We’re making use of the numColumns prop for FlatList to render three contacts in every row.
The renderFavoriteThumbnail method is responsible for every item in the list where we use our
ContactThumbnail component to display the user’s avatar. Notice how we’ve also passed in a
navigate action to the onPress prop. This allows the user to navigate to the contact’s profile screen
by pressing an avatar on the Favorites screen - just like when they press a contact on the Contacts
screen.
Since we’re using FlatList again, we can hook up a keyExtractor method once more:

Navigation

348

contact-list/5/screens/Favorites.js

import ContactThumbnail from '../components/ContactThumbnail';
const keyExtractor = ({ phone }) => phone;
export default class Favorites extends React.Component {

And we can finish things off here by adding a few styles:
contact-list/5/screens/Favorites.js

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

Try it out
In a moment, we’ll build the Users screen then add tab navigation to our app. Before we do, let’s
add Favorites to our stack navigator just so we can see what it looks like. We’ll set it as our initial
route:
contact-list/routes.js

import { StackNavigator } from 'react-navigation';
import Contacts from './screens/Contacts';
import Profile from './screens/Profile';
import Favorites from './screens/Favorites';
export default StackNavigator(
{
Contacts: {
screen: Contacts,
},
Profile: {
screen: Profile,

349

Navigation

},
Favorites: {
screen: Favorites,
},
},
{
initialRouteName: 'Favorites',
},
);

Favorites

Note that pressing on any avatar navigates to the Profile screen.
As Favorites will not be a part of our stack navigation, go ahead and revert the changes here to the
original route configurations.

User screen
Let’s now build the third root screen, User, which displays the details of the user of the app. We’ll
begin with its imports:

Navigation

350

contact-list/5/screens/User.js

import React from 'react';
import { StyleSheet, Text, View, ActivityIndicator } from 'react-native';
import ContactThumbnail from '../components/ContactThumbnail';
import colors from '../utils/colors';
import { fetchUserContact } from '../utils/api';

We’re using another method, fetchUserContact, from our API utility file. This fetches a single
contact.
We define the header styles at the top of the component:
contact-list/5/screens/User.js

export default class User extends React.Component {
static navigationOptions = {
title: 'Me',
headerTintColor: 'white',
headerStyle: {
backgroundColor: colors.blue,
},
};

Similar to the Profile screen, we’re displaying a blue header bar with white text.
Our local state and componentDidMount method will follow the same pattern as other screens:
contact-list/5/screens/User.js

state = {
user: [],
loading: true,
error: false,
};
async componentDidMount() {
try {
const user = await fetchUserContact();
this.setState({
user,
loading: false,
error: false,

Navigation

351

});
} catch (e) {
this.setState({
loading: false,
error: true,
});
}
}

Our render method will show the ContactThumbnail with the user’s name, avatar and phone
number:
contact-list/5/screens/User.js

render() {
const { loading, user, error } = this.state;
const { avatar, name, phone } = user;
return (

{loading && }
{error && Error...}
{!loading && (

)}

);
}

And finally, we’ll style our container to have a blue background as well as place our thumbnail in
the center of our screen:
contact-list/5/screens/User.js

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

Navigation

Try it out
We’ll again temporarily modify our routes.js file to test out this component:
contact-list/routes.js

import { StackNavigator } from 'react-navigation';
import Contacts from './screens/Contacts';
import Profile from './screens/Profile';
import User from './screens/User';
export default StackNavigator(
{
Contacts: {
screen: Contacts,
},
Profile: {
screen: Profile,
},
User: {
screen: User,
},
},
{
initialRouteName: 'User',
},
);

352

353

Navigation

User

Nested navigators
Now that we have all the screens that make up our tabs, we can start putting our tab navigation
logic in place. Let’s import and use the TabNavigator component in our routes.js file:
contact-list/routes.js

import { TabNavigator } from 'react-navigation';
import Contacts from './screens/Contacts';
import Favorites from './screens/Favorites';
import User from './screens/User';
export default TabNavigator({
Contacts: {
screen: Contacts,
},
Favorites: {
screen: Favorites,
},

354

Navigation

User: {
screen: User,
},
});

If we run the app, we’ll see tabs with labels at the bottom of the iOS device screen and at the top of
the Android screen. These tabs allow us to switch between the three screens.

Tabs

We’ll modify the styling of our tabs in a little bit. At the moment, we have a bigger problem. We
don’t have our header component anymore and if we try pressing any of the contacts in the Contacts
or Favorites screens, nothing happens. This is because we’ve removed our StackNavigator entirely
and only have a single tab navigation system in place. What we want to do is compose our two
navigators.
Let’s update our route configurations. We’ll begin with the imports since we’ll be including a few
new ones:

355

Navigation

contact-list/5/routes.js

import React from 'react';
import { StackNavigator, TabNavigator } from 'react-navigation';
import { MaterialIcons } from '@expo/vector-icons';
import
import
import
import

Favorites from './screens/Favorites';
Contacts from './screens/Contacts';
Profile from './screens/Profile';
User from './screens/User';

import colors from './utils/colors';

We’re importing both navigators from react-navigation as well as MaterialIcons from Expo’s
vector-icons package. This package is a wrapper around react-native-vector-icons80 , a library
that contains a number of vector icons. Creating icons is done by using JSX to define icon
components. For this reason, we’ve imported React to this file as well.
You can find a list of all the icons provided by the library here81 . There are a number
of different icon sets that can be used (such as FontAwesome). We’ll only be using the
MaterialIcons icon set for this application.

To compose stack navigation and tab navigation, each tab will have its own separate navigation
stack. This means that instead of passing specific screens to each tab, we’ll pass in a stack (a
StackNavigator component) that contains every possible screen within that tab.
For example, we want the contacts list to still use a stack navigator. That way, the user can navigate
to individual profiles. That stack navigator will reside inside our app’s broader tab navigator.
Let’s write our stack navigator for the contacts list. It looks the same as before, with one additional
configuration:
contact-list/5/routes.js

const ContactsScreens = StackNavigator(
{
Contacts: {
screen: Contacts,
},
Profile: {
screen: Profile,
},
},
80 https://github.com/oblador/react-native-vector-icons
81 https://expo.github.io/vector-icons/

Navigation

356

{
initialRouteName: 'Contacts',
navigationOptions: {
tabBarIcon: getTabBarIcon('list'),
},
},
);

For the “Contacts” tab, we want to first show the Contacts screen and allow the user to be able to
navigate to the Profile screen.
Notice how we’ve also added navigation options to specify this navigator’s tabBarIcon. We’re
passing in a getTabIcon helper method to retrieve a list icon component. Let’s define this function
right after our imports:
contact-list/5/routes.js

import colors from './utils/colors';
const getTabBarIcon = icon => ({ tintColor }) => (

);
const ContactsScreens = StackNavigator(

The tabBarIcon option expects a function. It will call that function with a single object that has the
properties focused and tintColor.
The getTabIcon function returns a function that returns a specific icon component from the
MaterialIcons icon set given its name. When we define our TabNavigator component, we’ll assign
the tint colors for all icons in each of our tabs.
Higher-order functions
The tabBarIcon parameter in our navigation options expects a function that takes an object with
focused and tintColor as attributes. It looks like the following:
tabBarIcon: (params) => (

),

357

Navigation

The focused argument allows us to render something different depending on whether the current
tab is focused in view or not. We’re not doing anything for the different states so we do not even
use focused at all. We only use tintColor in order to give our tab icons an appropriate active or
inactive color defined as options in TabNavigator.
With parameter context matchinga , we can simplify how we pass in our arguments:
tabBarIcon: ({ tintColor }) => (

),

Since every icon in each of the tabs have the same tint color and size, we simplify how we render
our icons by defining a single getTabIcon function:
const getTabBarIcon = icon => ({ tintColor }) => (

);
// ...
tabBarIcon: getTabBarIcon('list'),

The getTabIcon function takes an icon string as a parameter and returns a function that takes the
correct parameters expected by tabBarIcon (an object with tintColor and focused).
a appendix_higher_order_components

Let’s do the same for our other two tabs, Favorites and User:
contact-list/5/routes.js

const FavoritesScreens = StackNavigator(
{
Favorites: {
screen: Favorites,
},
Profile: {
screen: Profile,
},
},
{
initialRouteName: 'Favorites',

Navigation

358

navigationOptions: {
tabBarIcon: getTabBarIcon('star'),
},
},
);
const UserScreens = StackNavigator(
{
User: {
screen: User,
},
},
{
initialRouteName: 'User',
navigationOptions: {
tabBarIcon: getTabBarIcon('person'),
},
},
);

For the Favorites tab, the idea is similar. We want to default to the Favorites screen but still allow
the user to navigate to the Profile screen for any specific contact. The User stack navigator only
has one screen at the moment, but we’ll add a second screen a little later.
Now that we’ve defined our stack navigators, we can write up our tab navigator underneath:
contact-list/5/routes.js

export default TabNavigator(
{
Contacts: {
screen: ContactsScreens,
},
Favorites: {
screen: FavoritesScreens,
},
User: {
screen: UserScreens,
},
},
{
initialRouteName: 'Contacts',
tabBarPosition: 'bottom',
tabBarOptions: {

359

Navigation

style: {
backgroundColor: colors.greyLight,
},
showLabel: false,
showIcon: true,
activeTintColor: colors.blue,
inactiveTintColor: colors.greyDark,
renderIndicator: () => null,
},
},
);

We pass each StackNavigator as the screen for their corresponding tab. React Navigation allows us
to pass entire navigators as a tab screen and the first screen of the stack will be the default screen
for that tab. We’ve also defined some configurations for our tabs:
• tabBarPosition allows us to have our tabs at the top or bottom of our screen. For iOS, this
defaults to bottom and Android defaults to top. Specifying bottom means the tabs will render
at the bottom for both platforms.
• tabBarOptions allow us to modify styling for our tabs. We first define the tab background color
using the style object. Since we only want icons to show, we’ve also specified showLabel to be
false and showIcon to true. We set our icon colors for both active (where the user is currently
viewing) and inactive tabs. The last option, renderIndicator, allows us to pass in a function
to modify how tab indicators (or lines at the bottom of the active tab) are rendered. A default
tab indicator shows for Android and we pass null to remove it entirely.

With regards to tabBarOptions, there are options specific to Android that do not apply
to iOS. Every option we’ve specified so far is customizable for both platforms. For more
information on tab configurations and options, refer to the documentation82 .

Try it out
If we try running the application now, we’ll see our complete tab logic working!
82 https://reactnavigation.org/docs/navigators/tab

360

Navigation

Contacts

Try navigating between the different tabs as well as pressing a contact list item or thumbnail to
navigate to the Profile screen. You’ll notice that navigating to the Profile screen in one tab will
only show that screen there. Composing both tab and stack navigation allows for more complex
navigation architectures where each stack in a tab maintains the history of its own navigated screens
independently from the others.

Modal navigation
We’ll introduce our final screen in the app, Options, to demonstrate how stack navigators can be
modified to render new screens with a modal. This is only for iOS and does not work for Android.
We can begin by creating an Options.js file in the screens/ directory:

Navigation

361

contact-list/screens/Options.js
1
2
3

import React from 'react';
import { StyleSheet, View } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';

4
5
6

import DetailListItem from '../components/DetailListItem';
import colors from '../utils/colors';

7
8
9
10
11
12
13
14
15
16
17
18
19

export default class Options extends React.Component {
static navigationOptions = ({ navigation: { goBack } }) => ({
title: 'Options',
headerLeft: (
 goBack()}
/>
),
});

20

render() {
return (





);
}

21
22
23
24
25
26
27
28
29
30

}

31
32
33
34
35
36
37

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
});

In this screen, we render a few DetailListItem components to represent options that the user
can press to modify their profile settings. We’ve included a headerLeft attribute for the screen’s

Navigation

362

navigation options that renders a close icon. We’ve added a callback to the icon’s onPress prop that
fires navigate.goBack to close the current screen and return to the previous one.
Let’s build the functionality to allow the user to navigate to this screen. We’ll do this in the User
screen by adding an icon to our header:
contact-list/screens/User.js

static navigationOptions = ({ navigation: { navigate } }) => ({
title: 'Me',
headerTintColor: 'white',
headerStyle: {
backgroundColor: colors.blue,
},
headerRight: (
 navigate('Options')}
/>
),
});

Don’t forget to import MaterialIcons in this file.
Now, in routes.js, we’ll import our Options screen:
contact-list/routes.js

import Options from './screens/Options';

Then add it to the UserScreens stack navigator within routes.js:
contact-list/routes.js

const UserScreens = StackNavigator(
{
User: {
screen: User,
},
Options: {
screen: Options,
},
},
{

363

Navigation

mode: 'modal',
initialRouteName: 'User',
navigationOptions: {
tabBarIcon: getTabBarIcon('person'),
},
},
);

We use the mode attribute to specify this navigator should have modal transitions for iOS.
Try it out
Start the app and give it a shot! You’ll be able to navigate directly to the Options screen through
User.

User

By pressing the icon on the right of our header bar, you’ll notice that the screen moves up from the
bottom if you own an iOS device.

364

Navigation

Options

If you try on Android device or emulator, the screen will fade in just like any of the other screens
in the stack.

Drawer navigation
Another navigation pattern that is commonly used is drawer navigation, where views are accessible
through a drawer that slides in from the left side of the screen.

365

Navigation

Navigation drawer - (from Material Design documentation - Components)

Just like tab navigation, a drawer navigator allows users to switch between equally important views
quickly.
Neither of these patterns is better than the other. Choosing the right pattern depends on both
preferences as well as the number of root screens that the user can access.
A good rule of thumb is that tabs work well when there are three to five of them. If there are many
important, unrelated screens that the user should be able to access without navigating through a
stack, then drawer navigation may be more suitable.
Although our final app only includes tab navigation for our three core views, let’s explore what
using drawer navigation would look like. We’ll swap in DrawerNavigator for TabNavigator.
Let’s modify our routes.js file:
contact-list/6/routes.js
1
2
3

import React from 'react';
import { StackNavigator, DrawerNavigator } from 'react-navigation';
import { MaterialIcons } from '@expo/vector-icons';

4
5
6
7
8
9

import
import
import
import
import

Favorites from './screens/Favorites';
Contacts from './screens/Contacts';
Profile from './screens/Profile';
User from './screens/User';
Options from './screens/Options';

10
11

const getDrawerItemIcon = icon => ({ tintColor }) => (

Navigation
12
13


);

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

const ContactsScreens = StackNavigator(
{
Contacts: {
screen: Contacts,
},
Profile: {
screen: Profile,
},
},
{
initialRouteName: 'Contacts',
navigationOptions: {
drawerIcon: getDrawerItemIcon('list'),
},
},
);

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

const FavoritesScreens = StackNavigator(
{
Favorites: {
screen: Favorites,
},
Profile: {
screen: Profile,
},
},
{
initialRouteName: 'Favorites',
navigationOptions: {
drawerIcon: getDrawerItemIcon('star'),
},
},
);

48
49
50
51
52
53
54

const UserScreens = StackNavigator(
{
User: {
screen: User,
},
Options: {

366

Navigation
55
56
57
58
59
60
61
62
63
64
65

367

screen: Options,
},
},
{
mode: 'modal',
initialRouteName: 'User',
navigationOptions: {
drawerIcon: getDrawerItemIcon('person'),
},
},
);

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

export default DrawerNavigator(
{
Contacts: {
screen: ContactsScreens,
},
Favorites: {
screen: FavoritesScreens,
},
User: {
screen: UserScreens,
},
},
{
initialRouteName: 'Contacts',
},
);

Note that instead of the tabBarIcon option, we use drawerIcon to display an icon near the menu item
within the drawer. We also change the name of the method that returns our icon from getTabIcon
to getDrawerIcon.
Although the drawer can be accessed by swiping on the left edge of the device screen towards the
right, it also helps to have a menu icon in each of the main screens that can open and close the
drawer. We’ll begin with the Contacts screen:

Navigation

368

contact-list/6/screens/Contacts.js

static navigationOptions = ({ navigation: { navigate } }) => ({
title: 'Contacts',
headerLeft: (
 navigate('DrawerToggle')}
/>
),
});

We’ve added a menu icon on the left hand side of the header. With React Navigation, we can navigate
to DrawerOpen and DrawerClose to open and close the drawer respectively. DrawerToggle will fire
either of those depending on the current state of the navigation drawer. This allows us to toggle the
drawer with a single method.
We can add this same icon to the Favorites screen:
contact-list/6/screens/Favorites.js

static navigationOptions = ({ navigation: { navigate } }) => ({
title: 'Favorites',
headerLeft: (
 navigate('DrawerToggle')}
/>
),
});

And the User screen:

Navigation

369

contact-list/6/screens/User.js

static navigationOptions = ({ navigation: { navigate } }) => ({
title: 'Me',
headerTintColor: 'white',
headerStyle: {
backgroundColor: colors.blue,
},
headerLeft: (
 navigate('DrawerToggle')}
/>
),
headerRight: (
 navigate('Options')}
/>
),
});

Try it out
Try running the application with these drawer settings enabled. We can see a menu icon in either
of our three root screens.

370

Navigation

Contacts Screen - Drawer

We can open and close our drawer by swiping right on the left edge of the screen or by pressing the
menu icon.

371

Navigation

Drawer

Sharing state between screens
So far, we’ve built our entire application using local component state. While doing so, we noticed
some of the challenges that applications with multiple screens have when data must be shared
between screens.
For example, our Favorites screen uses the same list of contacts as the Contacts screen. But we had
to make an API call for each screen, fetching the list twice. It would be better to fetch the list just
once and share data between screens.
There are a few different ways we can make this better. One way is to define all the data within
our application in the App component. We can then use the initialRouteParams property that React
Navigation provides for our root tab navigator to assign the state to its initial route - Contacts.
With this approach, we can continue to pass our entire data to every other screen we navigate using
navigation parameters similar to how we pass contact information from Contacts to Profile.
This method of maintaining state is not ideal due to the fact that every screen now has access to all
the data in the entire app. Moreover, this will most likely create performance issues as we would
need to re-render every screen to reflect changes to the state being modified in a specific screen.

Navigation

372

State containers
React Native applications that contain a navigation architecture generally handle data flow differently than we’ve done in our apps so far. So far, we’ve stored data in the root and screen components
of our apps, and we’ve passed data down from parent to child as props. In this app, we’ll use a
state container to manage all of our application data in a separate external location outside of our
components.
This can be useful to separate the UI and data concerns in our application. In a typical application
with multiple top-level screens, this approach allows us to pass parts of our state to each of our
screens.
Third-party libraries
One approach to including a state container is to use a third-party library. Redux83 and MobX84 are
two popular examples that allow users to maintain their entire application state in a single location.
They also impose certain restrictions on how this state object can be modified.
Using a community-supported library means we don’t have to spend the time trying to build the
logic ourselves. There are packages that exist in both the Redux and MobX ecosystem that allow
us to bind our React or React Native components directly to the global store. Modifying our state
requires an action to be dispatched which gives us more explicit control to modify our state in only
specific parts of our application. We can also take advantage of middlewares to intercept any of
our actions before it reaches our state. This can allow us to log all of our state changes for easier
debugging as well as fire asynchronous operations as part of our actions if we need to.
Downsides of using an external library include the learning curve needed to learn its API and specific
requirements. Redux, for example, adheres to the use of pure functions (or functions without any
side effects) and requires a decent amount of boilerplate code in order to connect a component to
our store with appropriate actions. MobX uses the concept of reactive programming and observables
in order to have our state update when our data is changed. Using either of these libraries or another
state management tool altogether means that we would need to fully understand how they work.
Custom state container
Instead of using a community-supported library, we always have the option of building our own
state container in our application. This can be useful when we want complete control over how we
manage our data, or when we don’t want to introduce a complex dependency.
While suitable for our needs in this app, a simple state container built from scratch will not have
nearly as many features or plugins as a third-party library. Most medium and large React Native
applications use Redux or MobX. These libraries have a bit of a learning curve and require more
boilerplate code, but they make things more predictable by constraining how we manage our state.
83 https://github.com/reactjs/redux
84 https://github.com/mobxjs/mobx

Navigation

373

They can help us build consistent applications that are easier to test, debug and analyze using
different built-in or external plugins.
To demonstrate the overall approach of how to manage state in a central location with navigation,
we’ll use an extremely simple state container of our own instead of relying on a third-party solution.
Copy over the the contact-list/store.js file in the sample code to the root of you application. If
you like, take a look at the file to get a better understanding of how it works.
In this file, we’ve defined all of our application state as a single object called state:
contact-list/store.js
1
2
3
4
5
6
7

let state = {
isFetchingContacts: true,
isFetchingUser: true,
contacts: [],
user: {},
error: false,
};

Each of the field values in this object are the default values when our application is first launched.
We then export three methods that can be used throughout our application:
• getState returns the application state.
• setState takes in new values and updates our state. We don’t mutate the current state object
directly, but instead create a new copy with our updated values.
• onChange allows us to listen for changes in our state.
Let’s begin by including it to our Contacts screen. We’ll start with importing our store:
contact-list/7/screens/Contacts.js

import store from '../store';

Now we can make use of our store methods:

Navigation

374

contact-list/7/screens/Contacts.js

export default class Contacts extends React.Component {
static navigationOptions = {
title: 'Contacts',
};
state = {
contacts: store.getState().contacts,
loading: store.getState().isFetchingContacts,
error: store.getState().error,
};
async componentDidMount() {
this.unsubscribe = store.onChange(() =>
this.setState({
contacts: store.getState().contacts,
loading: store.getState().isFetchingContacts,
error: store.getState().error,
}));
const contacts = await fetchContacts();
store.setState({ contacts, isFetchingContacts: false });
}
componentWillUnmount() {
this.unsubscribe();
}

We’ve set up our local state by connecting its attributes to the correct global store attributes. Notice
how we’ve defined loading to be equal to the global state attribute of isFetchingContacts. This
component does not need to know anything else in the state that it is not using (and this includes
the isFetchingUser boolean that would be used in the User screen). We have complete control of
how we want to refer and define our state relevant to the context of this component.
In componentDidMount, we set our onChange method to update our local component state using
this.setState. When we get the results of our API call, we use our store setState method to
update our shared store and since we also use onChange - our local component state will also update
to reflect this change. We make sure to unsubscribe from our change listener when our component
unmounts as well.
Now in our render method, we’ll need to update which parameters we look for within our state:

Navigation

contact-list/7/screens/Contacts.js

render() {
const { contacts, loading, error } = this.state;
const contactsSorted = contacts.sort((a, b) =>
a.name.localeCompare(b.name));
return (

{loading && }
{error && Error...}
{!loading &&
!error && (

)}

);
}

Let’s do the same thing for Favorites:
contact-list/7/screens/Favorites.js

export default class Favorites extends React.Component {
static navigationOptions = {
title: 'Favorites',
};
state = {
contacts: store.getState().contacts,
loading: store.getState().isFetchingContacts,
error: store.getState().error,
};
async componentDidMount() {
const { contacts } = this.state;
this.unsubscribe = store.onChange(() =>
this.setState({

375

376

Navigation

contacts: store.getState().contacts,
loading: store.getState().isFetchingContacts,
error: store.getState().error,
}));
if (contacts.length === 0) {
const fetchedContacts = await fetchContacts();
store.setState({ contacts: fetchedContacts, isFetchingContacts: false });
}
}
componentWillUnmount() {
this.unsubscribe();
}

We technically shouldn’t need to submit fetch our contacts from the API again. We know that when
the user loads the application for the first time, the contacts are retrieved within the Contacts screen,
which is the first tab. However, it’s generally better not rely on the order each screen loads to ensure
our data is ready. This is both for testability (we can easily run this screen individually), and because
if we later add the capability to navigate to this screen without loading the Contacts screen first,
our application will fail. A good example of when this might happen is when we allow a user to
deep link to a specific screen from outside the application entirely. We’ll explore this concept in a
bit.
Similarly, we can update our render method as well:
contact-list/7/screens/Favorites.js

render() {
const { contacts, loading, error } = this.state;
const favorites = contacts.filter(contact => contact.favorite);
return (

{loading && }
{error && Error...}
{!loading &&
!error && (

)}

);
}

And finally, we can update our User screen to follow this same approach:
contact-list/7/screens/User.js

export default class User extends React.Component {
static navigationOptions = {
title: 'Me',
headerTintColor: 'white',
headerStyle: {
backgroundColor: colors.blue,
},
};
state = {
user: store.getState().user,
loading: store.getState().isFetchingUser,
error: store.getState().error,
};
async componentDidMount() {
this.unsubscribe = store.onChange(() =>
this.setState({
user: store.getState().user,
loading: store.getState().isFetchingUser,
error: store.getState().error,
}));
const user = await fetchUserContact();
store.setState({ user, isFetchingUser: false });
}
componentWillUnmount() {
this.unsubscribe();
}

377

Navigation

378

render() {
const { user, loading, error } = this.state;
const { avatar, name, phone } = user;
return (

{loading && }
{error && Error...}
{!loading && (

)}

);
}
}

Don’t forget to import store within Favorites and User!
Try it out
If we try running the application at this point, we’ll notice everything works exactly the same. Again,
the difference now is that each top level screen in our application uses the same shared data object
in our application.
Instead of being more specific about which parts of our central store we wanted to connect to in
each screen, we could have just connected the entire store using state = store.getState();.
However, connecting only parts of the global state to our component not only improves how we
encapsulate which state parameters we need, but it can also improve re-render performance. In
React Native, a component re-renders if any part of its state changes. With this approach, we can
have our component re-render only when the state specific to it changes.
Although a centralized store allowed us to handle how data is managed across a number of top-level
screens, it’s important to remember that this adds an extra layer of abstraction to our application.
Not only do we now pass presentational data from parent components to child components, we
also pass data through navigation parameters when we navigate through certain screens and use a
top-level state container that manages all the data in our application.

Deep Linking
The last major topic we’ll explore in this chapter is deep linking. Deep linking means launching
the app and navigating to a specific screen automatically. A deep link bypasses the tab, stack, and

Navigation

379

drawer navigation, taking the user directly to the desired screen. This can be useful when launching
your app from a webpage, a push notification, or another app.
Imagine the user gets a push notification that a new contact has been added. When the user taps the
notification, they should be taken directly to the profile screen for that contact, rather than having
to navigate their way from the initial screen of the app.

Deep links are similar to typing a URI (Uniform Resource Identifier) into a web browser. On the web,
https://www.fullstackreact.com might load the homepage for the website, while https://www.fullstackreact.co
will load a different page. Similarly in mobile applications, we perform a deep link using a URI, where
each URI generally takes us to a different screen.
Using a navigation library for a mobile app helps us build our app in terms of screens – this makes
it easy to connect any screen of our app to a specific URI.
Let’s explore how we can add deep linking to our current application by allowing the user to navigate
to a specific contact’s profile directly. With Expo, the base URI is different based on the state of the
application:
• exp://localhost:19000/+ or exp://10.2.8.358:19000/+ is the URI we use during development. 10.2.8.358 is our IP address and 19000 is the port that the app is running. We can see
our IP address and the port right underneath the QR code printed to the terminal when we
start the application with yarn start.
• exp://exp.host/@fullstackio/contact-list/+ is what we can use if our app is published to
the Expo client. If you have the final version of this app installed on your device through the
client, try typing this address into a web browser on your mobile device and you’ll navigate
directly to it.
• For standalone apps published outside of Expo, the URI can be defined in app.json. For
example:
app.json

{
"expo": {
"scheme": "contact-list"
}
}

This will give us a URI of contact-list://+.
Instead of having to take care of all of these different possible URI values, Expo provides us with a
linkingUri attribute from a Constants object that will resolve to the correct URI depending on the
state of the application.
Now let’s begin adding deep linking to our contact list application! Our goal is to allow a user to navigate to a specific contact only using his or her first name. For example, exp://{linkingUri}/+?name=ali

Navigation

380

will navigate directly to Ali’s profile. If the contact doesn’t exist, we’ll have the user remain in the
Contacts screen and not be navigated anywhere. For a real production application however, it would
make more sense to show a user-friendly error message if this happens.
We’ll begin with importing Linking and Constants into our Contacts screen:
contact-list/screens/Contacts.js

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

The Linking API provides methods that allow us to handle incoming deep links as well as open
external links. We’ll add two of these methods to the lifecycle hook that fires after our component
mounts:
contact-list/screens/Contacts.js

async componentDidMount() {
this.unsubscribe = store.onChange(() =>
this.setState({
contacts: store.getState().contacts,
loading: store.getState().isFetchingContacts,
error: store.getState().error,
}));
const contacts = await fetchContacts();
store.setState({ contacts, isFetchingContacts: false });
Linking.addEventListener('url', this.handleOpenUrl);
const url = await Linking.getInitialURL();
this.handleOpenUrl({ url });
}

Let’s go over the two methods we added:

Navigation

381

• The getInitialURL method will fire when a URI associated with the app is accessed externally.
This method allows a user to deep link to a particular part of the application when the
app is closed and not running in the background. In here, we pass the URL obtained to a
handleOpenUrl method.
• For instances where the app is running in the background, we can listen to URL events and
provide a callback to handle these situations. This is why we use addEventListener and pass
a handler to the same handleOpenUrl method.
Like any event listener, we’ll need to make sure it is removed when our component is destroyed/unmounted:
contact-list/screens/Contacts.js

componentWillUnmount() {
Linking.removeEventListener('url', this.handleOpenUrl);
this.unsubscribe();
}

Now that we’ve handled incoming deep links in our component, let’s create our handler method to
respond appropriately to the correct URL:
contact-list/screens/Contacts.js

handleOpenUrl(event) {
const { navigation: { navigate } } = this.props;
const { url } = event;
const params = getURLParams(url);
if (params.name) {
const queriedContact = store
.getState()
.contacts.find(contact =>
contact.name.split(' ')[0].toLowerCase() ===
params.name.toLowerCase());
if (queriedContact) {
navigate('Profile', { id: queriedContact.id });
}
}
}

We use a getURLParams utility function to extract query parameters from a given string and return
an object. For example getURLParams(exp://localhost:19000/+?name=abby) will return {name:

382

Navigation

'abby'}. If the name parameter exists, we check our state for a contact with the same first name
and if so - navigate to the Profile screen for that user.

We’ll also need to import the getURLParams utility function at the top of our file:
contact-list/screens/Contacts.js

import getURLParams from '../utils/getURLParams';

Although deep linking to a contact by name is straightforward, this approach is flawed. If two
contacts have the same name, our logic will fail, always returning the first contact it finds that
meets the condition. Ideally, we would want to pass the contact’s ID as a URI parameter. In this
example application however, we generate random UUIDs for each contact every time we load our
application. For this reason, we’ve shown the name-based approach for simplicity.
Try it out
While developing locally, you can try deep linking with different methods depending on which
platform you’re using:
• If you’re using the iOS simulator or an actual device connected to the same network, you can
open Safari and type exp://localhost:19000/+?name=ali into the address bar.
• On an Android emulator on the same network, you can test it through a terminal command:

adb shell am start -W -a "android.intent.action.VIEW" -d "exp://localhost:19000/+?na\
me=ali"

If localhost:19000 isn’t working, the application may be running on a different port. Take
a look at the terminal to see which port is being used.

You’ll notice you’ll be navigated directly to his profile screen:

383

Navigation

Deep Linking

If we try navigating to a contact with a name that doesn’t exist, we’ll remain in the Contacts screen.

Summary
Navigation is one of the most crucial elements of building an application that requires multiple
screens. In this chapter, we looked at different navigation patterns as well as how they can be
composed to allow for a complete navigation system. We built out a complete contact list example
app to explore this in-depth, using all of the navigators provided by React Navigation. We then
moved on to building a small state container to further understand how data can be shared between
top-level screens. Finally, we finished the chapter by including deep linking functionality in a specific
part of our application.
While discussing the differences between native and JavaScript navigation libraries, we briefly
touched on how JavaScript implementaions use their own custom components by relying on React
Native components and the Animated API. Animations and gestures are important topics in mobile
development and the next two chapters will dive deeper into how they work in React Native.

Animation
In order to animate a component on the screen, we’ll generally update its size, position, color, or
other style attributes continuously over time. We often use animations to imitate movement in the
real world, or to build interactivity similar to familiar physical objects.
We’ve seen several examples of animation already:
• In our weather app, we saw how the KeyboardAvoidingView shrinks to accommodate room for
the keyboard.
• In our messaging app, we used the LayoutAnimation API to achieve similar keyboard-related
animations.
• In our contacts app, we used react-nagivation, which automatically coordinates transition
animations between screens.
In this chapter and the next, we’ll explore animations in more depth by building a simple puzzle game
app. React Native offers two main animation APIs: Animated and LayoutAnimation. We’ll use these
to create a variety of different animations in our game. Along the way, we’ll learn the advantages
and disadvantages of each approach.
The next chapter (“Gestures”) will primarily focus on a closely related topic: gestures. Gestures help
us build components that respond to tapping, dragging, pinching, rotating, etc. Combining gestures
and animations enables us to build intuitive, interactive experiences in React Native.

Animation challenges
Building beautiful animations can be tricky. Let’s look at a few of the challenges we’ll face, and how
we can overcome them.

Performance challenges
To achieve animations that look smooth, we’ll want our UI to render at 60 frames-per-second (fps).
In other words, we need to render 1 frame roughly every 16 milliseconds (1000 milliseconds / 60
frames). If we perform expensive computations that take longer than 16 milliseconds within a single
frame, our animations may start to look choppy and uneven. Thus, we must constantly pay attention
to performance when working with animation.
Performance issues tend to fall into a few specific categories:

385

Animation

• Calculating new layouts during animation: When we change a style attribute that affects
the size or position of a component, React Native usually re-calculates the entire layout of the
UI. This calculation happens on a native thread, but is still an expensive calculation that can
result in choppy animations. In this chapter, we’ll learn how we can avoid this by animating
the transform style attribute of a component.
• Re-rendering components: When a component’s state or props change, React must determine how to reconcile these changes and update the UI to reflect them. React is fairly
efficient by default, so components generally render quickly enough that we don’t optimize
their performance. With animation, however, a large component that takes a few milliseconds
to render may lead to choppy animations. In this chapter, we’ll learn how we can reduce rerenders with shouldComponentUpdate to acheive smoother animations.
• Communicating between native code and JavaScript: Since JavaScript runs asynchronously,
JavaScript code won’t start executing in response to a gesture until the frame after the gesture
happens on the native side. If React Native must pass values back and forth between the native
thread and the JavaScript engine, this can lead to slow animations. In this chapter, we’ll learn
how we can use useNativeDriver with our animations to mitigate this.

Complex control flow challenges
When working with animations, we tend to write more asynchronous code than normal. We must
often wait for an animation to complete before starting another animation (using an imperative API)
or unmounting a component. The asynchronous control flow and imperative calls can quickly lead
to confusing, buggy code.
To keep our code clear and accurate, we’ll use a state machine85 approach for our more complex
components. We’ll define a set of named states for each component, and define transitions from
one state to another. This is similar to the React component lifecycle: our component will transition
through different states (similar to mounting, updating, unmounting, etc), and we can run a specific
function every time the state changes.
If you’re familiar with state machines, you might be wondering how our state machine
approach will differ from normal usage of React component state. React state is an implicit
state machine, which we often use without defining specific states. In our case, we’re going to
be explicit about our states, naming them and defining transitions between them. Ultimately
though, we’re just using React state in a slightly more structured way than normal.
If you’re not familiar with state machines, that’s fine. We’ll be building ours together as we
go through the chapter. Even if you’re not familiar with the term “state machine,” the coding
style will probably look familiar, since we’ve used it elsewhere in this book already (e.g. the
INPUT_METHOD from the “Core APIs” chapter).
85 https://en.wikipedia.org/wiki/Finite-state_machine

Animation

386

Building a puzzle game
In this chapter, we’ll learn how to use the React Native animation APIs to build a slider-puzzle game.
You can try the completed app on your phone by scanning this QR code from within the Expo app:

Our app will have two screens. The first screen let’s us choose the size of the puzzle and start a new
game:

Animation

387

The second screen opens when we start the game. The goal of the game is to rearrange the squares
of the puzzle to complete the image displayed in the top left:

Project setup
In this chapter, we’ll work out of the sample code directory for the project. Throughout this book
we’ve already set up several projects and created many components from scratch, so for this project
the import statements, propTypes, defaultProps, and styles are already written for you. We’ll be
adding the animations and interactivity to these components as we go.
We’ll use the contents of puzzle/1 as a foundation for our app. First, you’ll need to copy the
puzzle/1 directory to somewhere else on your computer. Then in the terminal, you can navigate to
the directory you copied and install the dependencies by running yarn.
For example, if you unzipped the book’s sample code to ∼/Downloads/fsrn/, on a macOS or Linux
computer you would run:
$ cp -r ~/Downloads/fsrn/puzzle/1 ~/Downloads/puzzle
$ cd ~/Downloads/puzzle
$ yarn

388

Animation

You won’t be able to work out of the original directory, since it’s nested within another React
Native app directory, and the packager currently doesn’t support nested directories like this.
That’s why you’ll need to copy puzzle/1 elsewhere.

Once this finishes, choose one of the following to start the app:
• yarn start - Start the Packager and display a QR code to open the app on your phone
• yarn ios - Start the Packager and launch the app on the iOS simulator
• yarn android - Start the Packager and launch the app on the Android emulator
You should see a dark full-screen gradient (it’s subtle), which looks like this:

Project Structure
Let’s take a look at the files in the directory we copied:

Animation

├──
├──
├──
├──
│
│
│
├──
│
│
│
│
│
│
│
├──
├──
│
│
├──
│
│
│
│
│
│
│
│
├──
│
└──

389

App.js
README.md
app.json
assets
├── logo.png
├── logo@2x.png
└── logo@3x.png
components
├── Board.js
├── Button.js
├── Draggable.js
├── Logo.js
├── Preview.js
├── Stats.js
└── Toggle.js
package.json
screens
├── Game.js
└── Start.js
utils
├── api.js
├── clamp.js
├── configureTransition.js
├── controlFlow.js
├── formatElapsedTime.js
├── grid.js
├── puzzle.js
└── sleep.js
validators
└── PuzzlePropType.js
yarn.lock

Here’s a quick overview of the most important parts:
• The App.js file is the entry point of our code, as with our other apps.
• The assets directory contains a logo for our puzzle app.
• The components directory contains all the component files we’ll use in this chapter. Some of
them have been written already, while others are scaffolds that need to be filled out.
• The screens directory contains the two screen components in our app: the Start screen and
the Game screen. The App coordinates the transitions between these two screens.
• The utils directory contains a variety of utility functions that let us build a complex app like
this more easily. Most of these functions aren’t specific to React Native, so you can think of

Animation

390

them as a “black box” – we’ll cover the relevant APIs, but the implementation details aren’t
too important to understand.
• The validators directory contains a custom propTypes function that we’ll use in several
different places.
Now that we’re familiar with the project structure, let’s dive into the code!

App
Let’s walk through how the App component coordinates different parts of the app. Open up App.js.

App state
App stores the state of the current game and renders either the Start screen or the Game screen. A

“game” in our app is represented by the state of the puzzle and the specific image used for the puzzle.
To start a new game, the app generates a new puzzle state and chooses a new random image.
If we look at the state object, we can see there are 3 fields:
puzzle/1/App.js

state = {
size: 3,
puzzle: null,
image: null,
};

• size - The size of the slider puzzle, as an integer. We’ll allow puzzles that are 3x3, 4x4, 5x5,
or 6x6. We’ll allow the user to choose a different size before starting a new game, and we’ll
initialize the new puzzle with the chosen size.
• puzzle - Before a game begins or after a game ends, this value is null. If there’s a current game,
this object stores the state of the game’s puzzle. The state of the puzzle should be considered
immutable. The file utils/puzzle.js includes utility functions for interacting with the puzzle
state object, e.g. moving squares on the board (which returns a new object).
• image - The image to use in the slider puzzle. We’ll fetch this image prior to starting the game
so that (hopefully) we can fully download it before the game starts. That way we can avoid
showing an ActivityIndicator and delaying the game.

App screens
Our app will contain two screens: Start.js and Game.js. Let’s briefly look at each.
Start screen
Open Start.js. The propTypes have been defined for you:

391

Animation

puzzle/1/screens/Start.js

static propTypes = {
onChangeSize: PropTypes.func.isRequired,
onStartGame: PropTypes.func.isRequired,
size: PropTypes.number.isRequired,
};

When we write the rest of this component, we’ll be building the buttons that allow switching the
size of the puzzle board. We’ll receive the current size as a prop, and call onChangeSize when we
want to update the size in the state of App. We’ll also build a button for starting the game. When
the user presses this button, we’ll call the onStartGame prop so that App knows to instantiate a puzzle
object and transition to the Game screen.
The state object for this component includes a field transitionState:
puzzle/1/screens/Start.js

state = {
transitionState: State.Launching,
};

This transitionState value indicates the current state of our state machine. Each possible state is
defined in an object called State near the top of the file:
puzzle/1/screens/Start.js

const State = {
Launching: 'Launching',
WillTransitionIn: 'WillTransitionIn',
WillTransitionOut: 'WillTransitionOut',
};

This object defines the possible states in our state machine. We’ll set the component’s transitionState
to each of these values as we animate the different views in our component. We’ll then use
transitionState in the render method to determine how to render the component in its current
state.
We define the possible states as constants in State, rather than assigning strings directly
to transitionState, both to avoid small bugs due to typos and to clearly document all the
possible states in one place.

We can see that the Start screen begins in the Launching state, since transitionState is initialized
to State.Launching. The Start component will transition from Launching when the app starts, to

Animation

392

WillTransitionIn when we’re ready to fade in the UI, to WillTransitionOut when we’re ready to
transition to the Game screen.

We’ll use this pattern of State and transitionState throughout the components in this app to keep
our asynchronous logic clear and explicit.
Game screen
Now open Game.js. Again, the propTypes have been defined for you:
puzzle/1/screens/Game.js

static propTypes = {
puzzle: PuzzlePropType.isRequired,
image: Image.propTypes.source,
onChange: PropTypes.func.isRequired,
onQuit: PropTypes.func.isRequired,
};

The puzzle and image props are used to display the puzzle board. When we want to change the
puzzle, we’ll pass an updated puzzle object to App using the onChange prop. We’ll also present a
button to allow quitting the game. When the user presses this button, we’ll call onQuit, initiating a
transition back to the Start screen.
Like in Start.js, we’ll use a state machine to simplify our code:
puzzle/1/screens/Game.js

const State = {
LoadingImage: 'LoadingImage',
WillTransitionIn: 'WillTransitionIn',
RequestTransitionOut: 'RequestTransitionOut',
WillTransitionOut: 'WillTransitionOut',
};

We’ll cover these states in more detail when we build the screen.
Now that you have an overview of how the state and screens of our app will work, we can dive in
to building animations!

Building the Start screen
In order to build the Start screen, we’ll use the two main building blocks of animation: LayoutAnimation
and Animated. Each of these come with their own strengths and weaknesses. With LayoutAnimation
we can easily transition our entire UI, while Animated gives us more precise control over individual
values we want to animate.

Animation

393

Initial layout
Let’s use LayoutAnimation to animate the position of a logo from the center of the screen to the top
of the screen.
Initially we’ll show this:

Then we’ll animate the position of the logo to turn it into this:

394

Animation

We’re rendering placeholder buttons for now. We’ll style the buttons and add some custom
animations soon.

Open up Start.js. You’ll notice the component’s import statements, propTypes, state, and styles
are already defined.
Let’s start by returning the Logo and a few other components from our render method. Add the
following to render:
puzzle/screens/Start.js

// ...
render() {
const { size, onChangeSize } = this.props;
const { transitionState } = this.state;
return (




Animation

395







Source Exif Data:
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.5
Linearized                      : No
Modify Date                     : 2018:07:27 10:20:33-07:00
Create Date                     : 2018:07:27 17:02:16-00:00
Author                          : Devin Abbott and Houssein Djirdehh
Title                           : Fullstack React Native
Creator                         : LaTeX with hyperref package
Producer                        : XeTeX 0.99996
Page Count                      : 552
EXIF Metadata provided by EXIF.tools

Navigation menu