In the first part of this series, you learned how to set up React Native on your machine, create and use custom components, and use third party libraries, such as moment.js. In this tutorial, you learn how to make network requests using fetch
, render a web page using the built-in WebView
component, and run the app on a physical device.
1. Fetch API Wrapper
In the first part of this series, we used the api
function, but we haven't defined it yet. Start by creating a src directory and add a file to it, api.js. Open the file and add the following to it:
module.exports = function(url){ return fetch(url).then(function(response){ return response.json(); }).then(function(json){ return json; }); }
This file uses the fetch
function, which is by default available in React Native. This function allows the app to perform network requests. If you've used jQuery, it's pretty similar to the $.ajax
function. You specify a URL and some optional data, and you get a response back.
The only difference is that you need to do a bit of extra work. The function for capturing the first promise returns the raw response, which means that you have to call the json
method on the response
to get the promise that returns the JSON string. So you have to return the result from this and capture the promise by calling the then
function once again and pass in the function that will be called once the promise resolves.
The JSON string would then be passed as an argument to this function so we just return it. The fetch
method returns a promise so when we call the api
method, we still have to call the then
method to capture the actual response, just like we did in the first part of this series.
api(story_url).then( (story) => { ... } );
2. WebPage
Component
The WebPage
component is responsible for rendering a web page. It uses the WebView
component to do so.
var React = require('react-native'); var { AppRegistry, StyleSheet, Text, View, WebView } = React; var Button = require('react-native-button'); var GiftedSpinner = require('react-native-gifted-spinner'); var _ = require('lodash'); var WebPage = React.createClass({ getInitialState: function() { return { isLoading: true }; }, render: function(){ return (<View style={styles.container}> <View style={styles.webview_header}> <View style={styles.header_item}> <Button style={styles.button} onPress={this.back}>Back</Button> </View> <View style={styles.header_item}> <Text style={styles.page_title}>{this.truncate(this.state.pageTitle)}</Text> </View> <View style={[styles.header_item, styles.spinner]}> { this.state.isLoading && <GiftedSpinner /> } </View> </View> <View style={styles.webview_body}> <WebView url={this.props.url} onNavigationStateChange={this.onNavigationStateChange} /> </View> </View>); }, truncate: function(str){ return _.truncate(str, 20); }, onNavigationStateChange: function(navState) { if(!navState.loading){ this.setState({ isLoading: false, pageTitle: navState.title }); } }, back: function(){ this.props.navigator.pop(); } }); var styles = StyleSheet.create({ container: { flex: 1 }, webview_header: { paddingLeft: 10, backgroundColor: '#FF6600', flex: 1, justifyContent: 'space-between', flexDirection: 'row' }, header_item: { paddingLeft: 10, paddingRight: 10, justifyContent: 'center' }, webview_body: { flex: 9 }, button: { textAlign: 'left', color: '#FFF' }, page_title: { color: '#FFF' }, spinner: { alignItems: 'flex-end' } }); module.exports = WebPage;
First, we do some housekeeping by creating the variables we need and requiring the libraries we'll be using.
var React = require('react-native'); var { AppRegistry, StyleSheet, Text, View, WebView } = React; var Button = require('react-native-button'); var GiftedSpinner = require('react-native-gifted-spinner'); var _ = require('lodash');
Next, we create the WebPage
component.
var WebPage = React.createClass({ ... });
We set isLoading
to true
as the default state. This property is responsible for determining whether or not to show the spinner. By default, the spinner should be visible to indicate that the page is loading.
getInitialState: function() { return { isLoading: true }; },
Next, we render the component. Like the news item component, this one also has a header and a body. The header contains a back button, the title of the page, and a spinner.
render: function(){ return (<View style={styles.container}> <View style={styles.webview_header}> <View style={styles.header_item}> <Button style={styles.button} onPress={this.back}>Back</Button> </View> <View style={styles.header_item}> <Text style={styles.page_title}>{this.truncate(this.state.pageTitle)}</Text> </View> <View style={[styles.header_item, styles.spinner]}> { this.state.isLoading && <GiftedSpinner /> } </View> </View> <View style={styles.webview_body}> <WebView url={this.props.url} onNavigationStateChange={this.onNavigationStateChange} /> </View> </View>); },
The body contains the WebView
component. The WebView
component has a url
and onNavigationStateChange
attributes. The url
is the URL that was passed from the viewPage
function in the NewsItems
component earlier. So when the following code is executed:
this.props.navigator.push({name: 'web_page', url: url});
The renderScene
method in index.android.js also gets executed and the URL is passed to it:
renderScene: function(route, navigator) { var Component = ROUTES[route.name]; return ( <Component route={route} navigator={navigator} url={route.url} /> ); },
That is how we have access to the URL by extracting it from the props: this.props.url
.
Let's go back to the attributes added to the WebView
component. We have the onNavigationStateChange
attribute, which is used for specifying the function to execute whenever the web view navigates to a new page. This is what that function looks like:
onNavigationStateChange: function(navState) { if(!navState.loading){ this.setState({ isLoading: false, pageTitle: navState.title }); } },
When the above function is called, the navState
is passed along as an argument. This contains information about the current state of the web view, such as the title of the page and whether or not it is currently loading. This is the perfect place to update the state. When the page is no longer loading, we set isLoading
to false
and set a value for the pageTitle
.
Next, we have the back
function, which makes the navigator go back one page. This gets called whenever the user taps the back button in the header.
back: function(){ this.props.navigator.pop(); }
The truncate
function limits the length of whatever is passed into the function. We use this function to limit the text for the page title of the web page.
truncate: function(str){ return _.truncate(str, 20); },
The stylesheet looks like this:
var styles = StyleSheet.create({ container: { flex: 1 }, webview_header: { paddingLeft: 10, backgroundColor: '#FF6600', flex: 1, justifyContent: 'space-between', flexDirection: 'row' }, header_item: { paddingLeft: 10, paddingRight: 10, justifyContent: 'center' }, webview_body: { flex: 9 }, button: { textAlign: 'left', color: '#FFF' }, page_title: { color: '#FFF' }, spinner: { alignItems: 'flex-end' } });
Lastly, expose the component to the outside world:
module.exports = WebPage;
3. Running the App
To run the app, you need an Android device or an emulator. If you want to use an emulator, I recommend using Genymotion. You can run the app by executing the following command:
react-native run-android
This command installs and launches the app. But you will get the following error if you try to do so:
This is because React Native expects the React server to be running on your machine. The React server compiles the app every time you save the changes in your text editor. The react-native run-android
command is only used for running the app for the purpose of testing and debugging the app. That's why it's dependent on the React server for actually compiling the app.
To get rid of the error, you need to run the react-native start
command to start the server. This takes a while on the first run, but when it gets to the part where it says the following:
<END> Building Dependency Graph (35135ms)
You can open a new terminal window on your project directory and execute adb shell input keyevent 82
. This opens the developer menu in the device or emulator. Once the menu is opened, select dev settings then select debug server host & port.
This opens a prompt asking you to enter the ip address and port of your computer. Find out the internal IP address of your computer and enter it in the prompt along with the port 8081, which is the default port on which React server runs. In other words, if your IP address is 192.168.254.254, then enter 192.168.254.254:8081.
After that, go back to the developer menu and select reload JS. This reloads the app so it detects the running React server instance. The app should how be working without problems.
If you want to test on an iOS device, then follow the guide on the React Native website.
4. Next Steps
We have built a pretty neat news reader app with React Native. What's next? Here are a few ideas if you want to learn more about React Native:
- Improve the code by breaking the app down in a few more reusable components. Start by looking at duplicated code. For example, in the app that we created, we have duplicated the header and the components inside it. What you can do is create a header component that accepts the title as the property and then requires it on every page where you need a header.
- Improve the response time of the app by creating a server that caches the items from the Hacker News API. This allows you to perform only one network request that contains all the news items instead of having to perform multiple network requests like we did in this tutorial.
- Generate a signed APK so that you can distribute the app on Google Play. For iOS, you can use Xcode to distribute your app to Apple's App Store.
- Explore the documentation for APIs that access native device capabilities, such as the camera.
- Check out the Awesome React Native repo on Github. That repo contains a list of components, resources, and tools that you can use with React Native.
- If you want to keep yourself updated with news about React Native, then subscribe to the React Native Newsletter.
Conclusion
That's it. In this tutorial, you learned how to work with React Native to create a news reader app that talks to the Hacker News API. If you have any questions, drop them in the comments below and I'll try my best to answer them. You can find the source files of this tutorial on GitHub. Thanks for reading.
Comments