DEV Community

Cover image for How to build a news app with JavaScript and React Native
Mohammed Salman
Mohammed Salman

Posted on • Updated on

How to build a news app with JavaScript and React Native

Requirements for building the app:

  • A basic understanding of the JavaScript language.
  • Node.js, and react native.
  • Libraries used: moment, react-native, react-native-elements.

If you’re not familiar with these resources, don’t worry — they are quite easy to use.

The topics we will cover in the post are:

  • News API
  • Fetch API
  • FlatList
  • Pull down to refresh
  • Linking

And more…so let’s get started!
You can find the full project repo HERE.

News API

A simple and easy-to-use API that returns JSON metadata for headlines and articles live all over the web right now. — NewsAPI.org

First, you should go ahead and sign up for News Api to get your free apiKey (your authentication key).

Create a new React Native project, and call it news_app (or whatever you want). In the project directory, make a new folder and call it src . In src create a folder an name it components . So your project directory should look something like this:
screenshot
In the src folder, create a new file called news.js . In this file we are going to fetch the JSON that contains the headlines from the News API.

news.js

const url =
  "https://newsapi.org/v2/top-headlines?country=us&apiKey=YOUR_API_KEY_HERE";

export async function getNews() {
  let result = await fetch(url).then(response => response.json());
  return result.articles;
}
Enter fullscreen mode Exit fullscreen mode

Make sure you replace YOUR_API_KEY_HERE with your own API key. For more information about the News API, go to newsapi docs.

Now we declare the getNews function, which is going to fetch the articles for us. Export the function so we can use it in our App.js file.

App.js

import React from 'react';
import { FlatList } from 'react-native';

// Import getNews function from news.js
import { getNews } from './src/news';
// We'll get to this one later
import Article from './src/components/Article';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { articles: [], refreshing: true };
    this.fetchNews = this.fetchNews.bind(this);
  }
  // Called after a component is mounted
  componentDidMount() {
    this.fetchNews();
   }

  fetchNews() {
    getNews()
      .then(articles => this.setState({ articles, refreshing: false }))
      .catch(() => this.setState({ refreshing: false }));
  }

  handleRefresh() {
    this.setState(
      {
        refreshing: true
    },
      () => this.fetchNews()
    );
  }

  render() {
    return (
      <FlatList
        data={this.state.articles}
        renderItem={({ item }) => <Article article={item} />}
        keyExtractor={item => item.url}
        refreshing={this.state.refreshing}
        onRefresh={this.handleRefresh.bind(this)}
      />
  );
  }
}
Enter fullscreen mode Exit fullscreen mode

In the constructor, we define the initial state. articles will store our articles after we fetch them, and refreshing will help us in refresh animation. Notice that I setrefreshing to true, because when we start the app, we want the animation to start while we load the articles.
componentDidMount is invoked immediately after a component is mounted. Inside it we call the fetchNews method.

componentDidMount() {
  this.fetchNews();
}
Enter fullscreen mode Exit fullscreen mode

In fetchNews we call getNews() which returns a promise. So we use the .then() method which takes a callback function, and the callback function takes an argument (the articles).

Now assign the articles in the state to the articles argument. I only typed articles because it’s a new ES6 syntax that means { articles: articles } , and we set refreshing to false to stop the spinner animation.

fetchNews() {
  getNews().then(
      articles => this.setState({ articles, refreshing: false })
  ).catch(() => this.setState({ refreshing: false }));
}
Enter fullscreen mode Exit fullscreen mode

.catch() is called in rejected cases.

handleRefresh starts the spinner animation and call fetchNews(). We pass () => this.fetchNews() , so it’s called immediately after we assign the state.

handleRefresh() {
  this.setState({ refreshing: true },() => this.fetchNews());
}
Enter fullscreen mode Exit fullscreen mode

In the render method, we return a FlatList element. Then we pass some props. data is the array of articles from this.state. The renderItem takes a function to render each item in the array, but in our case it just returns the Article component we imported earlier (we’ll get there). And we pass the article item as a prop to use later in that component.

Article.js

In src/components create a new JavaScript file and call it Article.js.

Let’s start by installing two simple libraries using npm: react-native-elements, which gives us some premade components we could use, and moment that will handle our time.

run using terminal/cmd:

npm install --save react-native-elements moment

In Article.js:

import React from 'react';
import { View, Linking, TouchableNativeFeedback } from 'react-native';
import { Text, Button, Card, Divider } from 'react-native-elements';
import moment from 'moment';

export default class Article extends React.Component {
  render() {
    const {
      title,
      description,
      publishedAt,
      source,
      urlToImage,
      url
    } = this.props.article;
    const { noteStyle, featuredTitleStyle } = styles;
    const time = moment(publishedAt || moment.now()).fromNow();
    const defaultImg =
      'https://wallpaper.wiki/wp-content/uploads/2017/04/wallpaper.wiki-Images-HD-Diamond-Pattern-PIC-WPB009691.jpg';

    return (
      <TouchableNativeFeedback
        useForeground
        onPress={() => Linking.openURL(url)}
      >
        <Card
          featuredTitle={title}
          featuredTitleStyle={featuredTitleStyle}
          image={{
            uri: urlToImage || defaultImg
          }}
        >
          <Text style={{ marginBottom: 10 }}>
            {description || 'Read More..'}
          </Text>
          <Divider style={{ backgroundColor: '#dfe6e9' }} />
          <View
            style={{ flexDirection: 'row', justifyContent: 'space-between' }}
          >
            <Text style={noteStyle}>{source.name.toUpperCase()}</Text>
            <Text style={noteStyle}>{time}</Text>
          </View>
        </Card>
      </TouchableNativeFeedback>
    );
  }
}

const styles = {
  noteStyle: {
    margin: 5,
    fontStyle: 'italic',
    color: '#b2bec3',
    fontSize: 10
  },
  featuredTitleStyle: {
    marginHorizontal: 5,
    textShadowColor: '#00000f',
    textShadowOffset: { width: 3, height: 3 },
    textShadowRadius: 3
  }
};
Enter fullscreen mode Exit fullscreen mode

There is a lot going on here. First, we start by destructuring the article prop and the styles object defined below the class.

In render we define time to store the time for when the article was published. We use the moment library to convert the date to the time passed since then, and we pass publishedAt or time from now if publishedAt is null.

defaultImg is assigned an image URL in case the URL of the article image is null.

The render method returns TouchableNativeFeedback (use TouchableOpacity instead if it does not work on your platform) to handle when the user presses the card. We pass it some props: useForground which tells the element to use the foreground when displaying the ripple effect on the card, and onPress , which takes a function and executes it when the user presses the card. We passed () => Linking.openUrl(url) which simply opens the URL to the full article when we press the card.

The card takes three props: featuredTitle which is just a fancy title placed over the image you could use title instead if you want, featuredTitleStyle to style it, and image which is the article image from the article prop. Otherwise, if its null , it’s going to be the defaultImg.

..
  featuredTitle={title}
  featuredTitleStyle={featuredTitleStyle}
  image={{ uri: urlToImage || defaultImg }}
..
Enter fullscreen mode Exit fullscreen mode

As for the text element, it will hold the description for the article.

<Text style={{ marginBottom: 10 }}>{description}</Text>

We added a divider to separate the description from time and source name.

<Divider style={{ backgroundColor: '#dfe6e9' }} />

Below the Divider , we have a View that contains the source name and the time the article was published.

..
<View 
  style={{ flexDirection: row, justifyContent: space-between }} > 
  <Text style={noteStyle}>{source.name.toUpperCase()}</Text>
  <Text style={noteStyle}>{time}</Text>
</View>
..
Enter fullscreen mode Exit fullscreen mode

After the class, we defined the styles for these components.

Now if we run the app:
screenshot
and we can refresh the app
screenshot
There you go! The source code for the app is available on GitHub HERE you can improve upon it and make a pull request😄.

I hope you enjoyed my article! If you have any questions at all, feel free to comment or reach me on twitter and I will definitely help :)

Also don't forget to share the article😄👇.

Top comments (21)

Collapse
 
ethan profile image
Ethan Stewart

Great post! I might have to go play with this news API sometime.

One possible improvement in code clarity: in your getNews function, should this

let result = await fetch(url).then(response => response).then(response => response.json());

be replaced with this?

let result = await fetch(url).then(response => response.json());

Since you're just passing it along to the next then call, it seems redundant to me and could be removed to make the code a little more clear. If you do have a reason for doing it that way, I'd love to hear why!

Collapse
 
msal profile image
Mohammed Salman

thanks, i might've forgot that i added the await keyword.

Collapse
 
drakexiang profile image
DrakeXiang

I'm stuck at the fetch section... It keeps giving me Network request failed error, however I can manage to request using the same code in chrome console or using other api, any idea?

Collapse
 
msal profile image
Mohammed Salman

what is your device?

Collapse
 
drakexiang profile image
DrakeXiang

Thanks for reply, I was developing with Genymotion, I tried a real device after you brought it up and it worked.. that's weird, thanks anyway :)

Collapse
 
skptricks profile image
skptricks

Hello,
I have found similar article related to react native, hope this will be helpful to create first reactapp : skptricks.com/2018/06/how-to-creat...

Collapse
 
fadhilshu profile image
fadhilshu

What are you use of theme for syntax in your Code EDITOR

Collapse
 
msal profile image
Mohammed Salman • Edited

Sorry for the late reply, I use Relaxed color theme, indent-rainbow and Rainbow Brackets plugins for VS Code:)

Collapse
 
fadhilshu profile image
fadhilshu • Edited

What are you use of theme for this syntax in your Code Editor?
thepracticaldev.s3.amazonaws.com/i...

Collapse
 
jaakap profile image
Jaakap

Thank You Mohammed.. I will give it a try 👏🏽👏🏽

Collapse
 
msal profile image
Mohammed Salman

Sure thing, thanks for reading👍

Collapse
 
kishanjvaghela profile image
Kishan Vaghela

Hello All, I have implemented CardView for react-native with elevation, that support android(All version) and iOS. github.com/Kishanjvaghela/react-na.... Check this out

Collapse
 
rokkoo profile image
Alfonso

Thx Mohammed, nice post!

Collapse
 
msal profile image
Mohammed Salman

happy you liked it:)

Collapse
 
kwabenberko profile image
Kwabena Bio Berko

Hey Mohammed, you might want to hide your api key?

Collapse
 
msal profile image
Mohammed Salman

Thanks, i fixed the issue:)

Collapse
 
matperera profile image
matperera

Can I search for specific articles in the api

Some comments may only be visible to logged-in visitors. Sign in to view all comments.