Redux is an important part of React Native ecosystem. If your world revolves around JavaScript, I am sure by now you have heard about Redux. Before reading the rest of the tutorial and going further just try to remember from time to time that you are only learning about redux because it will make things for you easier and not difficult. Now let us learn why do we need the Redux in one of your application.
Building a React or React Native application in the real world can become complex if there is not a proper way to handle data. If ever, at any point the data is not managed things will go out of your hand. If you are familiar with Reactjs or React Native, you know the default way of handling data is to keep it in a component state and pass it to the children components as props.
State and Props are the only two ways to control data in a component. Props are short for properties and it is a simple rule to follow in React world that we should mutate or change the value of props. In React world the flow of data is unidirectional or one way. That is, the data can always be passed from a parent to a child component. Take a look below at the simple example:
In the above example, we create two components: Parent and Child and assume in separate files. The Parent component consists of a view where a Child component is rendered. In the child component, the view renders a text message that is incoming from the props. The incoming message is available as the data in the state of the parent component.
This way, the child component can be reused with other parent components such that each parent component can have its own data to render. Do note that, at any point, we are not modifying the value of this.props
.
The state is there to mutate data. This is the only reason that the state exists within each component. Whenever we want to change the state we use this.setState()
method within a component. This method re-renders the component and all of its child component to reflect the changes. This works both in React and React Native similarly, however, the internals are different.
Since we can manage state and props so efficiently within a React Native app, where does the necessity of using Redux? Well, the above example is a bare minimum and not at all a real-time scenario. Imagine an application like Instagram or Twitter where you have different screens and each screen may depend on a component or two like the Parent and the reusable Child components from our example. It will be hard to keep track of the state of each component.
Redux is one the most widely adopted and a way of handling data. It enables the state to be shared a global attribute that an entire React Native application can use it and receive it in in the form of props. This is known as creating a store in terms of Redux. Redux simplifies the state by moving it in one place.
Redux uses an underlying React Mechanism called context. We are not going to dwell what is context since it is out of the scope of this article. I just wanted you to know that nothing magical or paranormal is happening behind the scenes.
Just remember the following terms since we are going to see them in action in the tutorial below:
- Actions
- Reducers
- Store
The key to learning Redux is by practice. I am not going to share any more information or visual graphics for the reason that it will only overwhelm things right now. Let us begin by creating a demo application to learn redux.
To build this application, I am going to use the latest tool introduced by @Expo team called expo-cli. Install it as a global dependency and then initialize a new React Native project using it.
To see if everything is working correctly at this initial state, run the following command.
You will be prompted with the following interface. Take some time to go through it. If you have build applications using Expo XDE or Create-React-Native-App before, you will there is not much change except that now the Expo-CLI makes use of Chrome browser.
Choose a simulator or device that can run Expo Client as marked in the above image. If you get the below screen that means our React Native project has been initialised without any difficulties.
With that, create the following files and folders inside the components
directory. I will explain later why we are following this directory structure later in this tutorial. For now, our initial setup is complete and we can start building our application.
First, we will create a dumb Timer component and connect it with App.js
. Add the following code to the Timer/index.js
.
Next, modify the App.js
file.
We will now make a static Timer component to see how things fit in. Start by modifying the StatusBar
. Then we are defining two Text
elements from react-native
library to specify where the actual timer will be displayed and where the buttons for starting and stopping the timer will be displayed. For now, both are text fields.
In this section, we are going to replace the section that displays Start and Stop Buttons!
with actual buttons. We will be using TouchableOpactiy
to make this work. A TouchableOpacity
component act as a wrapper for making views respond properly to touches. The opacity of the wrapped view or the button in our case gets decreased whenever a user touches it.
We are creating a reusable component since we need two buttons: Start and Stop.
This is a stateless component, see no class because we only need it to represent the Button in the UI of our app. We are also importing FontAwesome icons from @expo/vector-icons
which is a fork of react-native-vector-icons and comes directly with the expo SDK. No need to install it as a separate dependency. To display an icon, we to define its size
.
Lastly, in the above stateless component, we are defining propTypes
. I will be discussing how and why we should use PropTypes in a React Native application in another article.
In a mobile app, events are triggered due to touches and to handle those events we are going to use onPress
. We will have only two events here, Start and Stop. Both the buttons in our app are going to make use of onPressOut
which differs from onPress
. The onPressOut
is called whenever the touch is released by the user, that is, the user has released pressing the button. It is called before onPress
and is more accurate in a situation like ours where we need to start or stop the timer as soon as the user is done by pressing the button.
We will now require this Button
component in our Timer component.
So far, our Timer application does not do anything other than displaying a bare minimum UI. To make it work, we start by adding needed redux dependencies.
Now, let us start integrating Redux in our app.
In Redux, the state of the whole application is represented by one JavaScript object. Think of this object as read-only since we cannot make changes to this state (which is represented in the form of a tree) directly. We nees actions
to do so.
Actions are like events in Redux. They can be triggered in the form of mouse clicks, key presses, timers or network requests. The nature of each event mentioned is mutable. An action is a JavaScript object. To define an action, there is one requirement that is each action much have its own type property. We define these types in a file called types.js
:
Our application needs only three actions so far. The type of any action is a string value and are defined as constants.
In the file actions.js
we will require these types to define action creators. Actions Creators are functions that create actions.
The receiver of the action is known as a reducer. Whenever an action is triggered, the state of the application changes. The handling of the application's state is done by the reducers.
Reducer is a pure function that calculates the next state based on the initial or previous state. It always produces the same output if the state is unchanged. It takes two inputs: state and action must return the default state.
In our initial state, we define three attributes: isPlaying
, elapsedTime
and timerDuration
which currently has a default value of 6 (seconds) for testing purposes but the actual value we are going to change later is 25
(or 1500 seconds). Then, there are three helper functions: applyStartTimer
that will start the timer, applyRestartTimer
that will stop the timer function set everything to default and lastly, applyAddSecond
function which checks if time passed by is less than the total timer's duration, add one more second to increase its value. If the not, return the default state and stop the timer function from running.
After that, we define our reducer function and export the same function. Observe how the reducer function is organized. This is a pattern followed by most community memebers I have seen on the internet.
With the help of the reducer and the initial state, we can create the store object.
A store is an object that brings and actions and reducers together. It provides and holds state at the application level instead of individual components. Redux is not an opinionated library in terms which framework or library should use it or not, to bind a React or React Native application with Redux is done react-redux
module. This is done by using a high ordered component Provider
. It basically, passes the store down to the rest of the application.
We need to bind action creators with our Timer function in order to make it fully working and respond to the touchable events or start or restart the timer. We will be doing this in Timer/index.js
function. First, we import our the required dependencies to bind action creators.
bindActionCreators
maps action functions to an object using the names of the action functions. These functions automatically dispatch the action to the store when the function is called. To change the data we need to dispatch an action. For this to enable, we need to things: mapStateToProps
, mapDispatchToProps
and connect both of them with our component. This is the boilerplate code that you will be re-writing.
We define these two functions and modify our export default
statement after we define the styles for our React Native views.
mapStateToProps
is an object that lives in the store whose keys are passed down to the component as props. The below is the complete code for the Timer component.
I have created a custom function called formatTime
to display time in the correct format but you can make you use of any timer library. Next, to increment the value of time, I am using React lifecycle method componentWillReceiveProps
. I know it is going to deprecated soon, but for now it works. See our mini-app in action below:
For brevity and demo purpose I am using only seconds to display the timer. You can increase the value of the timer by editing the value of constant TIMER_DURATION
in reducers.js
.
We have reached the end of the article. Hopefully, you have had fun reading it as much as I enjoyed writing it.