Creating a Cordova Hybrid App with React, Redux and Webpack
In this article we will take a look at how to create a Cordova hybrid app using Onsen UI and React. We will create a full-fledged weather application that uses Redux to manage the state and Onsen UI to create a beautiful UI.
In a previous article we learned how to use Redux to manage the React application state. We have also learned how to use the Redux DevTools and hot reloading to easily debug React applications. This article will build on the things we learned in the previous articles so if you haven’t already, please read those first.
The source code for this example is available in this GitHub repository and you can try out the demo here.
Running the app with Cordova CLI
The repository is a Cordova project so you can try running it on your device. There is a hook that runs the Webpack build every time cordova prepare
is executed so there is no need to build manually every time you deploy.
To run the app with Cordova you first need to clone the repo and install the dependencies.
git clone git@github.com:argelius/react-onsenui-redux-weather.git
cd react-onsenui-redux-weather/
npm install
Now, assuming that you have Cordova installed, you can add a platform and run the app:
cordova platform add android
cordova run android
You can of course also run the app on iOS or any other platform that Cordova supports.
Code structure
We will structure our code in the same way as the previous article. Even if the app we made previously was very simple, the structure we used scales very well and can be used for applications with a large number of components and containers.
The project will have the following directory structure:
index.js
- The entry point of the app. Imports the root component and renders the app.reducers/
- Reducer functions. A reducer is a pure function that takes the current state and an action as arguments and returns the next state. This is the only way to update the Redux store.actions/
- Contains action creators. An action creator is a function that returns an action that can be dispatched to update the Redux store state tree.api/
- This directory contains code that wraps the weather API. For this app I have used the Open Weather Map API which is a great free alternative.components/
- This directory contains dumb components. A dumb component is a component that is just renders props to a view but doesn’t perform any particular actions.containers/
- A container is a component that is connected to the Redux store. It can dispatch actions and access the state.
I will not go into detail on what the difference between the components and containers are, or how reducers and action creators are used since we have already introduced these concepts in the previous blog post.
As a build tool I am using Webpack. This is just my personal preference, this code will work with Browserify as well.
If you are interested you can take a look at the Webpack config used in the project by clicking this link. This config includes React Hot Loader which really helps speed up development. I have also made a Webpack configuration for production which is used when I deploy to GitHub pages or build as a Cordova app.
Action creators
This app fetches data from a remote API, so we need a way to dispatch an action asynchronously. This is different from the previous app we made where every action was synchronous. To enable asynchronous actions we are using the Redux Thunk library.
The library is a middleware so it can be added using the applyMiddleware
function when creating the Redux store using the createStore
function:
This middleware enables action creators to return a function instead of an action. This can be used to delay dispatching an action. The obvious use case is to delay an action until an asynchronous API call has been resolved (or rejected if something went wrong).
In this app there is an action creator called fetchWeather
which uses the Thunk middleware:
As we can see the fetchWeather
action creator runs the queryWeather
function to fetch the current weather data from the Open Weather Map API.
The rest of the action creators are very simple. This is the whole actions/index.js
file that exports all action creators:
As we can see there are actions for adding and removing locations, fetching and receiving weather data as well as showing and hiding the dialog.
Reducers
The corresponding reducers can be found in the reducers/
directory. The app uses the combineReducers
function to combine the following reducers:
locations
selectedLocation
dialog
The combineReducers
function takes a list of reducers and combines them into a new reducer. The resulting state tree will have the following structure:
{
locations: [...],
selectedLocation: SOME_ID,
dialog: false
}
NOTE: When using
combineReducers
it is important to not use the same action ID twice. For larger app it is a good idea to use a naming scheme where every child reducer has its own namespace to avoid collisions.
The code of the selectedLocation
and dialog
reducers is very simple.
The selectedLocation
reducer just handles a single string, which is the ID of the location that is currently selected:
Similarly, the dialog
reducer just handles a single boolean value which represents whether the dialog is shown or hidden:
Finally, the locations
reducer is pretty complicated since it can handle five different action IDs:
As you can see in the code above, I have split the reducer into two since some of the actions act on the list (ADD_LOCATION
and REMOVE_LOCATION
) while some (SET_FETCH_ERROR
, REQUEST_WEATHER
, RECEIVE_WEATHER
) act on a single item of the list.
API calls
In the fetchWeather
action creator in the code above, there is a call to a function called queryWeather
that returns a Promise. The code of this function can be found in api/index.js
and it’s basically just a wrapper that uses the new fetch API to request the latest weather data for a location:
The code actually makes two requests since it first needs to get the current weather and then get the 5-day forecast. I am using the fetch
function instead of XHR since it is a lot simpler, especially for fetching JSON data and since it already returns a Promise there is no need to create a Promise object.
Components
In this section I will take a look at some of the components used in the application. Some of them are very simple and require no explanation while some of them are more complex.
We will first take a look at the App
component. This is the root component of the app. It renders into a Navigator component which is an Onsen UI component that enables stack based navigation. Stack navigation is a common pattern in mobile apps where a new page is pushed on top of the previous page.
The Navigator component implements a very powerful API. It manages a stack of route objects which are rendered into pages using the renderPage
prop. The route objects can be structured in any way the developer wants, in this case they have the following structure:
{
component: MyComponent,
key: A_UNIQUE_KEY
}
In order to push a new page on top of the stack, the following code is used:
navigator.pushPage({component: MyComponent, key: 'my-component'});
NOTE: The
renderPage
function must return an Onsen UIPage
component.
The following dumb components are also used in the app but they require no further explanation. Please take a look at the source code for more information.
Forecast
- Renders the 5-day forecast on the bottom of the details page.MainPage
- Renders the list of locations and the controls for adding new locations.NavBar
- Renders the toolbar on the top of the pages.WeatherIcon
- Renders the icon based on the current weather.
If you take a look at any of these components you will see that the layout and design is created entirely using inline styles. The only CSS used in the app is the Onsen UI default CSS. I think it’s very convenient to put the styles in the same file as the JavaScript code, since it makes the component self-contained.
Containers
As mentioned earlier, containers are React components that are connected to the Redux store. They have access to the state tree (or part of it) and can dispatch events. To take a look at all the containers used in this app, please follow this link.
The containers used in this app are:
LocationList
- Renders a list of locations from the current state.Location
- Renders an item in the list of locations. When clicked it will navigate to a details page.AddLocation
- When clicked it opens a dialog where the user can add a new location. Renders as a Material Design floating action button on Android and a normal button on iOS.AddLocationDialog
- A dialog that can be used to add a new location to the list.WeatherPage
- A details page for a location that shows the current weather as well as a 5-day forecast.
In this article we will only examine the LocationList
and Location
. If anything is unclear please inspect the code of the other containers as well.
So let’s start by looking at the LocationList
container. As the name implies it renders a list of locations.
It uses the connect
function from the React Redux library to map the locations
state to the locations
prop so it can be accessed from the component. To render the list the List component from Onsen UI is used. The List
component needs to define two props: dataSource
and renderRow
which are used to render the list:
In the renderRow
prop above we see that the Location
container is rendered. This component represents an item in the location list:
I will not discuss the code of the rest of the containers since it wouldn’t add anything to the article. They basically follow the same structure as the two we have discussed. Please take a look at the source code if you are interested.
Conclusion
I hope this article has been interesting. The goal was to show how a quite complicated app can be created easily using React and Redux with the Onsen UI components. If you are interested in creating your own apps using these technologies you can use this project as a starting point.
If you want to get started quickly using Onsen UI and React, I recommend you to try out our new CLI tool. It uses Cordova and Webpack under the hood but hides a lot of the complexity so you can get started making hybrid apps in no time.
Installing it is as easy as doing:
[sudo] npm install -g monaca
This is a new tool so we are really looking forward to your feedback!
If you have any questions about using Onsen UI or any of our other tools, please feel free to ask in the forum. If you like Onsen UI, please give us a star on GitHub.