Tuesday, June 14, 2022
HomeWeb DevelopmentEasy methods to create a information feed in React Native

Easy methods to create a information feed in React Native


On this step-by-step tutorial, we’ll create a information feed software with minimal code utilizing React Native for iOS and Android. Our software will encompass two screens, the primary is the Dwelling display or the Feed display. The second Element display will deal with loading a single article, loading information for a particular class, or looking for information with a particular key phrase.

Yow will discover the entire supply code for this text on this repository. Let’s get began!

Organising a Information API account

To populate our software with information articles, we’ll use the Information API as our backend. The Information API supplies us with a REST API that we will use to get the most recent, breaking information headlines from all all over the world.

Earlier than you possibly can start utilizing the Information API, it’s essential to get an API token from the web site. Go to the Get began web page and click on on Get API Key. When you click on the button, you’ll be redirected to the registration web page. After filling in your particulars on the web page and registering efficiently, you will get your API token. Now, let’s take the API for a spin and check all the pieces it has to supply.

To check the API, we’ll use Postman, however you need to use the API platform of your selection. Alternately, you possibly can merely use curl in your terminal. You may both use the standalone Postman software in your OS, or just use the online app, which is what I choose.

For Feed, we’ll use the prime headlines endpoint, so let’s check it first:

Top Headlines Endpoint

We’re utilizing the next URL:

https://newsapi.org/v2/top-headlines?class=know-how&language=en

We’re passing two question parameters to the URL:

  • class=know-how: Will get all the highest headlines within the know-how class
  • language=en: Ensures we solely get information articles written in English

Every name to a Information API URL should comprise an apiKey, which is used to authenticate the consumer who’s sending the request and for analytics functions.

We will add our API key in an API name in one among two methods. For one, you possibly can immediately ship it as a question param within the URL itself:

https://newsapi.org/v2/top-headlines?class=know-how&language=en&apiKey=<YOUR_API_KEY>

Nonetheless, this technique will not be safe, and due to this fact will not be advisable. However, you need to use it for testing. Alternately, we will strive sending API_KEY contained in the request header utilizing the X-Api-key. For those who’re utilizing curl, you may add a request header just like the one beneath:

 curl https://newsapi.org/v2/top-headlines?class=know-how&language=en
   -H "X-Api-key: <YOUR_API_KEY>"

For those who’re utilizing Postman, then merely go to the Headers tab of request and add a key and worth, like within the screenshot above.

Now that we now have a response object from our API, let’s analyze it and see what it supplies for us. The response has an articles key, which is an array of objects the place every object is a information article.

A pattern of a information object is given beneath:

{
    "supply": {
        "id": null,
        "title": "digitalspy.com"
    },
    "writer": "Joe Anderton",
    "title": "Strolling Useless's Norman Reedus reveals Loss of life Stranding 2 existence - Digital Spy",
    "description": "The Strolling Useless star Norman Reedus reveals Loss of life Stranding 2 existence.",
    "url": "https://www.digitalspy.com/tech/a40064850/walking-dead-norman-reedus-death-stranding-2/",
    "urlToImage": "https://hips.hearstapps.com/digitalspyuk.cdnds.internet/16/28/1468254183-screen-shot-2016-07-11-at-171152.jpg?crop=1xw:0.8929577464788733xh;heart,prime&resize=1200:*",
    "publishedAt": "2022-05-21T10:12:41Z",
    "content material": "The Strolling Useless star Norman Reedus has seemingly revealed the existence of Loss of life Stranding 2.rnThe actor performed main character Sam Porter Bridges within the PlayStation and PC sport from Steel Gear So… [+2088 chars]"
}

As you possibly can see, we get a variety of knowledge for a single information article, however we’ll use title, url, urlToImage, publishedAt, and content material. Now that our backend is all arrange, let’s begin engaged on the cellular app.

Utility preview

Earlier than we truly write the code for our app, let’s talk about the options we need to construct. First, we’ll want a house display or feed the place we’ll present all the most recent information articles. On the prime of the listing, there might be a horizontal listing of stories class tags, which, when chosen, will load information for that particular class.

We’ll want a search bar on the prime of the display that the consumer can use to look information utilizing a particular key phrase. We’ll additionally implement shared transition navigation. When the consumer clicks on any information articles, the NewsDetail display will seem.

We’ll use Redux to handle state in our software. We need to persist the info between software lifecycles, so we’ll use Redux Persist.

The app UI will assist each darkish and light-weight mode. The ultimate UI we’re constructing will appear like the next photos:

Final UI Feed
Feed
Final UI Categories
Classes
Final UI Search
Search characteristic
Final UI Dark Light Mode
Darkish/ mild mode

Creating a brand new React Native challenge

Let’s begin by creating a brand new React Native challenge as follows:

npx react-native init <App-Title-Right here> --template react-native-template-typescript

I’ll use TypeScript, however be at liberty to make use of JavaScript. If you’re going through any points making a TypeScript template challenge, you possibly can confer with the docs.

With our challenge arrange, let’s begin writing our code. First, take away all of the boilerplate code in App.tsx and add the code beneath:

import React from 'react';
import {Textual content} from 'react-native';

const App: React.FC = () => {
  return <Textual content>That is Feed Display</Textual content>;
};

export default App;

Let’s begin by including state-management to our app. We have to set up some libraries to include Redux into our challenge:

yarn add @reduxjs/toolkit react-redux redux

Or, if you happen to choose npm, use the next command:

npm i @reduxjs/toolkit react-redux redux

Within the step above, we’re putting in @reduxjs/toolkit, react-redux, and redux. In Redux v6, the createStore technique is deprecated, and we have to use Redux Toolkit’s configureStore technique as an alternative.

Now, let’s create a Redux retailer and hook up some actions and reducers to it. Create a brand new folder within the root of the challenge listing and title it src. We’ll hold all our software code right here. Add the redux folder inside it, then create three recordsdata inside it, actions.ts, reducers.ts, and retailer.ts.

The listing construction ought to now appear like the next:

React Native Project Directory Structure

We’ll create an motion perform to get information articles from the API and retailer them in our retailer. For now, let’s simply return an empty array:

export const GET_NEWS_FEED = 'GET_NEWS_FEED';

export const getNewsFeed =
() => (dispatch) => {
      dispatch({
        sort: GET_NEWS_FEED,
        payload: [],
      });
};

Let’s create feedReducer for our newsFeed:

import {GET_NEWS_FEED} from './actions';

const initialState = {
  newsFeed: [],
};

const feedReducer = (
  state = initialState,
  motion: {sort: String; payload: any},
) => {
  swap (motion.sort) {
    case GET_NEWS_FEED:
      return {...state, newsFeed: motion.payload};
    default:
      return state;
  }
};
export default feedReducer;

Within the reducer, we now have the state variable newsFeed, which initially is an empty array. After we obtain the GET_NEWS_FEED motion, we set its worth to the payload handed in motion.

Now that we now have actions and reducers for our state, let’s create a Redux retailer and initialize it with the next values.

Create retailer.ts file and add the next code:

import {configureStore} from '@reduxjs/toolkit';
import {combineReducers} from 'redux';
import feedReducer from './reducers';

const rootReducer = combineReducers({
  feedReducer: feedReducer,
});

export const retailer = configureStore({
  reducer: rootReducer,
});

We’ve efficiently arrange the redux retailer, now, we simply have to go it into our software. Add the next code to your App.tsx file:

import React from 'react';
import {Textual content} from 'react-native';
import {Supplier} from 'react-redux';
import {retailer} from './src/redux/retailer';

const App: React.FC = () => {
  return (
    <Supplier retailer={retailer}>
      <Textual content>That is feed display</Textual content>
    </Supplier>
  );
};

export default App;

Fetching information from an API

Thus far, we now have a working Redux retailer. Let’s populate it with information articles. To implement community calls, we’ll use Axios, so let’s add it to the challenge:

yarn add axios

We have to create an axios occasion so we don’t have to deal with request headers each time we make an API name. Create a brand new folder named api in src and add a file named index.ts inside it:

Axios Instance Indexts

We’ll create an axios occasion contained in the index.ts file and export it as an apiClient variable:

import axios from 'axios';
const BASE_URL = 'https://newsapi.org/v2/';

export const apiClient = axios.create({
  baseURL: BASE_URL,
  timeout: 10000,
  headers: {
    'X-Api-key': '<Add-your-api-token-here>',
  },
});

Now, let’s make the most of this apiClient in our getNewsFeed motion. Make the next adjustments in your actions.ts file:

export const getNewsFeed = () => async (dispatch: Perform) => {
  strive {
    const res = await apiClient.get(
      'top-headlines?language=en&class=enterprise',
    );
    if (res.standing === 200) {
      dispatch({
        sort: GET_NEWS_FEED,
        payload: res?.knowledge?.articles,
      });
    } else {
      console.warn('One thing went flawed');
    }
  } catch (error) {
    console.error(error);
  }
};

Within the code above, we make an API name to get the highest headlines within the enterprise class. If we obtain a profitable response from the API, then we replace the newsFeed state with the brand new articles acquired.

We have to name this motion when our App is rendered the primary time. We’ll additionally show a listing of article titles. Earlier than we populate our newsFeed, let’s create recordsdata for various screens in our software.

Create a screens folder inside src and create a brand new folder named Feed inside it. Then, add two recordsdata contained in the Feed folder named index.tsx and kinds.ts:

Display Article Titles Folder

To deal with notches on  an iPhone, we’ll use a helper library referred to as react-native-iphone-x-helper. Set up it utilizing the command beneath:

yarn add react-native-iphone-x-helper

Let’s add some kinds for our display. Make the next adjustments in your feed/kinds.ts:

import {StyleSheet} from 'react-native';
import {getStatusBarHeight, isIphoneX} from 'react-native-iphone-x-helper';

export default StyleSheet.create({
  container: {
    flex: 1,
    flexGrow: 1,
    paddingTop: isIphoneX() ? getStatusBarHeight() + 20 : 20,
  },
  listing: {
    flex: 1,
    flexGrow: 1,
    paddingVertical: 8,
  },
});

Then, add the code beneath in your feed/index.tsx file:

import React, {useEffect} from 'react';
import {Textual content, useColorScheme, View} from 'react-native';
import {useDispatch, useSelector} from 'react-redux';
import {getNewsFeed} from '../../redux/actions';
import kinds from './kinds';

export const Feed: React.FC = () => {
  const {newsFeed} = useSelector((state: any) => state.feedReducer);
  const dispatch: Perform = useDispatch();

  useEffect(() => {
    dispatch(getNewsFeed());
  }, [dispatch]);
  const backgroundColor = useColorScheme() === 'darkish' ? '#000' : '#fff';
  return (
    <View model={[styles.container, {backgroundColor}]}>
      {newsFeed?.map((merchandise: any) => (
        <Textual content>{merchandise.title}</Textual content>
      ))}
    </View>
  );
};

Now, we have to render the feed element in our App element. Make the adjustments beneath in your App.tsx file:

import React from 'react';
import {Supplier} from 'react-redux';
import {retailer} from './src/redux/retailer';
import {Feed} from './src/screens/feed';

const App: React.FC = () => {
  return (
    <Supplier retailer={retailer}>
      <Feed />
    </Supplier>
  );
};
export default App;

Now, your UI ought to appear like the next picture:

Render Feed UI

We at the moment are capable of fetch a listing of the most recent information from all over the world and show it on the display. Subsequent, we’ll add some UI design.

Designing the information article card

At the moment, we’re exhibiting the title of the information on the display. We have to replace it to indicate a picture of the article. Above it, we’ll show the title of the article. On the backside, we’ll present the timestamp of the actual information/article.

Let’s substitute our map perform in feed/index.tsx with a Flatlist. Earlier than you bounce into implementing the UI, you first want so as to add some libraries that we’ll use to design our UI:

yarn add react-native-uuid react-native-linear-gradient second

We‘ll use react-native-uuid to generate a singular key for each information article element and react-native-linear-gradient to create a semi-transparent to clear overlay over the picture on which we’ll show the title of the article.

Lastly, we use second to transform the timestamp acquired from API to a human-readable format. Make the next adjustments to feed/index.tsx:

import uuid from 'react-native-uuid';
....
return (
  <View model={[styles.container, {backgroundColor}]}>
    <FlatList
      keyExtractor={() => uuid.v4()}
      showsVerticalScrollIndicator={false}
      knowledge={newsFeed}
      renderItem={({merchandise, index}: any) => (
        <NewsArticle put up={merchandise} />
      )}
      model={kinds.listing}
    />
  </View>
);

Within the code above, you possibly can discover we’re assigning a singular key to every merchandise rendered by the Flatlist element. Subsequently, React treats every article in a different way, and there must be no overlap within the UI. If this doesn’t make sense now, it should later when our UI logic will get a bit of extra difficult.

The code above may even throw an error saying “NewsArticle will not be outlined”. Let’s repair that by creating a brand new element for our information article.

Within the src listing of your challenge, create a brand new folder named parts, which is able to comprise all of the UI parts we’ll use all through the appliance. Now, create a brand new folder referred to as NewsArticle and add two recordsdata inside it referred to as index.tsx and kinds.ts:

New Components Folder Index TSX

Add the next code to kinds.ts:

import {Platform, StyleSheet} from 'react-native';

const boxShadow: any = Platform.choose({
  ios: {
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      top: 0,
    },
    shadowOpacity: 0.4,
    shadowRadius: 4,
  },
  android: {elevation: 6},
});

export default StyleSheet.create({
  container: {
    top: 240,
    marginBottom: 18,
    backgroundColor: '#eee',
    borderRadius: 24,
    marginHorizontal: 16,
    ...boxShadow,
  },
  imageContainer: {flex: 1},
  picture: {
    flex: 1,
    borderRadius: 24,
    top: 300,
  },
  titleContainer: {
    place: 'absolute',
    backside: 0,
    width: '100%',
    borderBottomLeftRadius: 24,
    borderBottomRightRadius: 24,
    top: 160,
    paddingLeft: 16,
    paddingRight: 10,
    justifyContent: 'flex-end',
    alignItems: 'flex-start',
  },
  textual content: {
    fontSize: 18,
    fontWeight: '600',
    lineHeight: 24,
    coloration: '#fff',
    paddingBottom: 24,
  },
  timestamp: {
    place: 'absolute',
    coloration: '#eee',
    fontSize: 12,
    fontWeight: '300',
    proper: 16,
    backside: 8,
  },
});

These are just a few fundamental kinds that we’ll use to design our NewsArticle element. Add the next code to your index.tsx file:

import second from 'second';
import React from 'react';
import {Picture, Textual content, TouchableOpacity} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import kinds from './kinds';

sort Publish = {
  title: string;
  urlToImage: string;
  publishedAt: string;
  url: string;
  writer: string;
};

export const NewsArticle: React.FC<{
  put up: Publish;
}> = ({put up}) => {
  return (
    <TouchableOpacity activeOpacity={1} model={kinds.container}>
      <Picture
        supply={{
          uri: put up?.urlToImage ?? 'https://picsum.photographs/800',
          cache: 'force-cache',
        }}
        resizeMode={'cowl'}
        model={kinds.picture}
      />
      <LinearGradient
        colours={['#0000', '#000A', '#000']}
        model={kinds.titleContainer}>
        <Textual content model={kinds.textual content}>{put up?.title}</Textual content>
        <Textual content model={kinds.timestamp}>
          {second(put up?.publishedAt).format('HH:MM DD, MMMM')}
        </Textual content>
      </LinearGradient>
    </TouchableOpacity>
  );
};

Within the code above, we’ve declared the NewsArticle element, which receives a put up prop, which is of sort Publish. The definition of Publish is given above the element.

For the UI, we wrapped the entire element inside TouchableOpacity, which we’ll use later within the tutorial to navigate the consumer to the NewsDetail display. Then, an Picture is displayed over the entire view. If we don’t get any urlToImage from API, then we present a random placeholder picture.

For the textual content, we now have the Textual content parts inside a LinearGradient element, which is positioned absolute over the Picture, making the textual content readable to the consumer and sustaining the distinction ratio within the textual content UI. We additionally use second to format the publishedAt property of the article and at last show the formatted worth.

After implementing the adjustments above in your code, your software UI ought to appear like the next:

Text Gradient Updated UI

We now have a working, readable information feed. Let’s add performance to refresh the web page when the consumer pulls up on the display. Fortunately, this has a reasonably easy implementation. We simply want to make use of the RefreshControl prop of the Flatlist element.

Make the next adjustments in your feed/index.tsx:

import React, {useCallback, useEffect, useState} from 'react';
import {FlatList, RefreshControl, useColorScheme, View} from 'react-native';
import {useDispatch, useSelector} from 'react-redux';
import {NewsArticle} from '../../parts';
import {getNewsFeed} from '../../redux/actions';
import kinds from './kinds';
import uuid from 'react-native-uuid';

export const Feed: React.FC = () => {
  const {newsFeed} = useSelector((state: any) => state.feedReducer);
  const dispatch: Perform = useDispatch();
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    dispatch(getNewsFeed(setIsLoading));
  }, [dispatch]);

  const handleRefresh = useCallback(() => {
    dispatch(getNewsFeed(setIsLoading));
  }, [dispatch]);
  const backgroundColor = useColorScheme() === 'darkish' ? '#000' : '#fff';
  return (
    <View model={[styles.container, {backgroundColor}]}>
      <FlatList
        keyExtractor={() => uuid.v4()?.toString()}
        showsVerticalScrollIndicator={false}
        knowledge={newsFeed}
        renderItem={({merchandise}: any) => <NewsArticle put up={merchandise} />}
        refreshControl={
          <RefreshControl refreshing={isLoading} onRefresh={handleRefresh} />
        }
        model={kinds.listing}
      />
    </View>
  );
};

Within the code above, we’ve added a state isLoading, which is true when information is being fetched by way of the getNewsFeed technique. Then, we added the RefreshControl element in Flatlist, which might be proven when isLoading is true and in addition when the consumer pulls the listing as much as refresh knowledge. Then, the handleRefresh technique might be triggered.

Discover that we’ve added a setIsLoading parameter contained in the getNewsFeed technique. However, we haven’t modified the strategy definition but. Let’s try this subsequent; add the next code in your actions.ts file:

export const getNewsFeed =
  (setIsLoading: Perform) => async (dispatch: Perform) => {
    strive {
      setIsLoading(true);
      const res = await apiClient.get(
        'top-headlines?language=en&class=enterprise',
      );
      setIsLoading(false);
      if (res.standing === 200) {
        dispatch({
          sort: GET_NEWS_FEED,
          payload: res?.knowledge?.articles,
        });
      } else {
        console.warn('One thing went flawed');
      }
    } catch (error) {
      console.error(error);
    }
};

The output might be one thing like the photographs beneath:

Change Method Definition UI

Persisting retailer knowledge

We now have a working information feed, however there’s a minor drawback. You would possibly discover that when the app is first loaded, the display is clean for a while. To resolve this drawback, we’ll use Redux Persist, which is a sort of a middleware for the Redux retailer that persists retailer knowledge, rehydrating and populating it.

After implementing Redux Persist, when the consumer closes and opens the app, they’ll see the earlier feed as an alternative of a clean display. If any new feed is offered, then that might be loaded.

First, add the dependencies to your challenge. To take action, execute the command beneath:

yarn add @react-native-community/async-storage redux-persist

We have to add Async Storage as a result of all of our retailer knowledge might be saved in it. When the app opens once more, the shop might be rehydrated utilizing the info inside it. Principally, Redux Persist makes use of AsyncStorage for storage in React Native.

As soon as the dependencies are added, we have to make some adjustments in our retailer.ts file:

import AsyncStorage from '@react-native-community/async-storage';
import {configureStore} from '@reduxjs/toolkit';
import {combineReducers} from 'redux';
import feedReducer from './reducers';
import {persistReducer, persistStore} from 'redux-persist';

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  whitelist: ['newsFeed'],
};

const rootReducer = combineReducers({
  feedReducer: persistReducer(persistConfig, feedReducer),
});

export const retailer = configureStore({
  reducer: rootReducer,
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware({
      serializableCheck: false,
    }),
});

export const persistor = persistStore(retailer);

Within the code above, we created a persistConfig object, which is answerable for the configuration of Redux Persist. Inside the article, there are two major key objects, storage and whitelist.

We’ll use storage as storage for our Redux retailer knowledge, and whitelist is an array of strings. These are the names of the reducers that we need to persist. If we go an empty array to whitelist, then all our reducers might be endured.

Subsequent, we create a endured reducer utilizing the persistReducer technique from Redux Persist and go the persistConfig as the primary argument. We then go the precise reducer we need to persist.

We’re passing serializableCheck: false within the middleware key of configureStore to suppress a warning that’s thrown after we use Redux Stick with Redux v6.

Ultimately, we create a endured copy of our retailer utilizing the persistStore technique and export it as persistor. We’ll use this in our subsequent step.

Lastly, to supply the endured knowledge to our software, we have to go it in our App.tsx file simply after the Redux retailer Supplier element.

Add the next code contained in the App.tsx file:

import React from 'react';
import {ActivityIndicator} from 'react-native';
import {Supplier} from 'react-redux';
import {PersistGate} from 'redux-persist/integration/react';
import {persistor, retailer} from './src/redux/retailer';
import {Feed} from './src/screens/feed';

const App: React.FC = () => {
  return (
    <Supplier retailer={retailer}>
      <PersistGate
        loading={<ActivityIndicator measurement={'giant'} coloration={'#000'} />}
        persistor={persistor}>
        <Feed />
      </PersistGate>
    </Supplier>
  );
};
export default App;

We’re passing endured retailer knowledge by way of the PersistGate element, and the endured retailer is coming from the persistor. There’s a loading prop within the PersistGate element that takes any JSX element as an argument, and that JSX element is proven till the shop is rehydrated.

With that, we’ve endured our retailer knowledge. Now, if you happen to shut the app and open it once more, you gained’t see the clean display.

Subsequent, we’ll implement class tags. Our app may have a horizontal listing of predefined classes of stories that customers can view. When the consumer faucets any class tag, then the information feed ought to replace and present information associated to that particular class solely.

Let’s begin by creating a knowledge supply for classes. Create a file named constants.ts within the src folder of your challenge, then add the next code to it:

interface NewsCategoryTypes {
  [key: string]: string;
}

export const NewsCategory: NewsCategoryTypes = {
  enterprise: 'enterprise',
  leisure: 'leisure',
  basic: 'basic',
  well being: 'well being',
  science: 'science',
  sports activities: 'sports activities',
  know-how: 'know-how',
};

Now, let’s show a listing of classes on the Feed display above the articles listing. Add the code beneath within the feed/index.tsx file:

import {NewsArticle, NewsTags} from '../../parts';
import {NewsCategory} from '../../constants';
....
export const Feed: React.FC = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [selectedCategory, setSelectedCategory] = useState(
    NewsCategory.enterprise,
  );
  ....
  useEffect(() => {
    dispatch(getNewsFeed(setIsLoading, selectedCategory));
  }, [dispatch, selectedCategory]);

  const handleRefresh = useCallback(() => {
    dispatch(getNewsFeed(setIsLoading, selectedCategory));
  }, [dispatch, selectedCategory]);

  return (
    <View model={[styles.container, {backgroundColor}]}>
      <NewsTags
          selectedCategory={selectedCategory}
          setSelectedCategory={setSelectedCategory}
      />
      ....
    </View>
  );
};

Within the code above, we launched a brand new state referred to as selectedCategory and gave it an preliminary worth of NewsCategory.enterprise. We additionally at the moment are passing selectedCategory in our getNewsFeed motion, and at last, we’re rendering the NewsTags element, which we haven’t created but.

Let’s try this now. Create a brand new folder referred to as NewsTags and add two recordsdata inside it, kinds.ts and index.tsx:

Newtags Folder

Add the code beneath to the kinds.ts file:

import {StyleSheet} from 'react-native';
export default StyleSheet.create({
  listing: {
    maxHeight: 40,
  },
  contentContainer: {
    paddingHorizontal: 24,
    alignItems: 'heart',
    justifyContent: 'heart',
  },
});

To show a horizontal listing of classes, add the next code within the NewsTags/index.tsx file:

import React from 'react';
import {FlatList} from 'react-native';
import {NewsCategory} from '../../constants';
import {Tag} from '../Tag';
import kinds from './kinds';

export const NewsTags: React.FC<{
  selectedCategory: String;
  setSelectedCategory: Perform;
}> = ({selectedCategory, setSelectedCategory}) => {
  return (
    <FlatList
      horizontal
      showsHorizontalScrollIndicator={false}
      knowledge={Object.keys(NewsCategory)}
      keyExtractor={(merchandise: string) => merchandise}
      renderItem={({merchandise}: any) => (
        <Tag
          class={merchandise}
          selectedCategory={selectedCategory}
          setSelectedCategory={setSelectedCategory}
        />
      )}
      model={kinds.listing}
      contentContainerStyle={kinds.contentContainer}
    />
  );
};

Within the code above, we render over all of the keys in our NewsCategory object. Inside renderItem, we now have handed the Tag element, which we haven’t created but. Let’s create it now. Add a folder referred to as Tag in parts and create two recordsdata as seen within the beneath picture:

Tag Folder Components

Paste the code beneath contained in the kinds.ts file:

import {StyleSheet} from 'react-native';
export default StyleSheet.create({
  container: {
    borderWidth: 1,
    borderColor: '#FFA500',
    borderRadius: 12,
    top: 28,
    paddingHorizontal: 14,
    justifyContent: 'heart',
    alignItems: 'heart',
    marginRight: 24,
    backgroundColor: '#FFA50066',
  },
  textual content: {
    fontSize: 14,
    fontWeight: '500',
  },
  chosen: {
    backgroundColor: '#FF8800',
    borderColor: '#FF6600',
  },
});

To truly render a Tag element, add the next code to the index.tsx file:

import React, {useCallback} from 'react';
import {Textual content, TouchableOpacity, useColorScheme} from 'react-native';
import kinds from './kinds';

export const Tag: React.FC<{
  class: String;
  selectedCategory: String;
  setSelectedCategory: Perform;
}> = ({class, selectedCategory, setSelectedCategory}) => {
  const textColor = useColorScheme() === 'darkish' ? '#fff' : '#000';
  const handlePress = useCallback(() => {
    setSelectedCategory(class);
  }, [category, setSelectedCategory]);
  return (
    <TouchableOpacity
      model={[
        styles.container,
        selectedCategory === category && styles.selected,
      ]}
      onPress={handlePress}>
      <Textual content model={[styles.text, {color: textColor}]}>{`${
        class.charAt(0).toUpperCase() + class.slice(1)
      }`}</Textual content>
    </TouchableOpacity>
  );
};

The code above is a straightforward view that renders a class title textual content. When pressed, it units the selectedCategory to that class.

Lastly, we’ll modify the getNewsFeed motion technique in order that it returns a information feed for a particular information class. Modify the getNewsFeed technique in actions.ts like beneath:

export const getNewsFeed =
  (setIsLoading: Perform, class: String = NewsCategory.enterprise) =>
  async (dispatch: Perform) => {
    strive {
      setIsLoading(true);
      const res = await apiClient.get(
        `top-headlines?language=en&class=${class}`,
      );
      setIsLoading(false);
      if (res.standing === 200) {
        dispatch({
          sort: GET_NEWS_FEED,
          payload: res?.knowledge?.articles,
        });
      } else {
        console.warn('One thing went flawed');
      }
    } catch (error) {
      console.error(error);
    }
};

Now, we will choose a particular information class for which we need to see the information. The output might be just like the picture beneath:

Select News Category Example

Looking out information with particular key phrases

The final characteristic we’ll implement in our software is looking for a bit of specific information utilizing a string search question. The logic could be very easy. We’ll have a TextInput on the prime of the display. When the consumer sorts one thing into the TextInput, we’ll question the NewsAPI search endpoint to get the search outcome, then present it on the feed.

First, let’s add the required redux actions . Add the next code to your actions.ts:

export const searchNews =
  (searchTerm: string = '', setIsLoading: Perform = () => {}) =>
  async (dispatch: Perform) => {
    strive {
      setIsLoading(true);
      const res = await apiClient.get(`all the pieces?q=${searchTerm}`);
      setIsLoading(false);
      if (res.standing === 200) {
        dispatch({
          sort: SEARCH_NEWS,
          payload: res?.knowledge?.articles,
        });
      } else {
        console.warn('One thing went flawed');
      }
    } catch (error) {
      console.error(error);
    }
};

export const resetSearchResults = () => (dispatch: Perform) => {
  dispatch({
    sort: RESET_SEARCH_RESULTS,
  });
};

We additionally want so as to add some further states to our reducers. Add the next code to your reducers.ts:

import {GET_NEWS_FEED, RESET_SEARCH_RESULTS, SEARCH_NEWS} from './actions';

const initialState = {
  newsFeed: [],
  searchResults: [],
};
const feedReducer = (
  state = initialState,
  motion: {sort: String; payload: any},
) => {
  swap (motion.sort) {
    case GET_NEWS_FEED:
      return {...state, newsFeed: motion.payload};
    case SEARCH_NEWS:
      return {...state, searchResults: motion.payload};
    case RESET_SEARCH_RESULTS:
      return {...state, searchResults: []};
    default:
      return state;
  }
};
export default feedReducer;

Now that our state is ready up, let’s implement the search perform. Add the code beneath in your feed/index.tsx:

import React, {useCallback, useEffect, useState} from 'react';
import {FlatList, RefreshControl, useColorScheme, View} from 'react-native';
import uuid from 'react-native-uuid';
import {useDispatch, useSelector} from 'react-redux';
import {NewsArticle, NewsTags, SearchInput} from '../../parts';
import {NewsCategory} from '../../constants';
import {getNewsFeed} from '../../redux/actions';
import kinds from './kinds';

export const Feed: React.FC = () => {
  const {newsFeed, searchResults} = useSelector(
    (state: any) => state.feedReducer,
  );
  const [isLoading, setIsLoading] = useState(false);
  const [selectedCategory, setSelectedCategory] = useState(
    NewsCategory.enterprise,
  );
  const [searchText, setSearchText] = useState('');
  const dispatch: Perform = useDispatch();
  useEffect(() => {
    dispatch(getNewsFeed(setIsLoading, selectedCategory));
  }, [dispatch, selectedCategory]);
  const handleRefresh = useCallback(() => {
    dispatch(getNewsFeed(setIsLoading, selectedCategory));
  }, [dispatch, selectedCategory]);
  const backgroundColor = useColorScheme() === 'darkish' ? '#000' : '#fff';
  return (
    <View model={[styles.container, {backgroundColor}]}>
      <SearchInput
        searchText={searchText}
        setSearchText={setSearchText}
        setIsLoading={setIsLoading}
      />
      {!searchText?.trim() && (
        <NewsTags
          selectedCategory={selectedCategory}
          setSelectedCategory={setSelectedCategory}
        />
      )}
      <FlatList
        keyExtractor={() => uuid.v4()?.toString()}
        showsVerticalScrollIndicator={false}
        knowledge={searchText?.trim() ? searchResults : newsFeed}
        renderItem={({merchandise, index}: any) => (
          <NewsArticle put up={merchandise} />
        )}
        refreshControl={
          <RefreshControl refreshing={isLoading} onRefresh={handleRefresh} />
        }
        model={kinds.listing}
      />
    </View>
  );
};

Discover that we’ve added a brand new element referred to as SearchInput. Let’s create it in our code. Contained in the parts folder, create a folder referred to as SearchInput and title the recordsdata inside it kinds.ts and index.tsx:

Search Input Components Folder

Add the beneath code to the kinds.ts file:

import {StyleSheet} from 'react-native';

export default StyleSheet.create({
  container: {
    top: 40,
    marginHorizontal: 24,
    marginBottom: 8,
    borderRadius: 20,
    paddingHorizontal: 24,
    fontWeight: '400',
  },
});

Earlier than writing the code for SearchInput, we first want to put in a library:

yarn add lodash

We’re utilizing lodash to implement the debounce technique on the searchInput’s onChangeText technique. Lastly, add the next code to the SearchInput/index.tsx file:

import React, {useCallback} from 'react';
import {TextInput, useColorScheme} from 'react-native';
import {useDispatch} from 'react-redux';
import {resetSearchResults, searchNews} from '../../redux/actions';
import kinds from './kinds';
import {debounce} from 'lodash';

export const SearchInput: React.FC<{
  searchText: string;
  setSearchText: Perform;
  setIsLoading: Perform;
}> = ({searchText, setSearchText, setIsLoading}) => {
  const backgroundColor = useColorScheme() === 'darkish' ? '#333' : '#ddd';
  const placeholderColor = useColorScheme() === 'darkish' ? '#eee' : '#111';
  const coloration = useColorScheme() === 'darkish' ? '#fff' : '#000';
  const dispatch: Perform = useDispatch();
  const searchForText = useCallback(
    debounce((textual content: string) => {
      if (textual content?.trim().size > 0) {
        dispatch(searchNews(textual content, setIsLoading));
      } else {
        dispatch(resetSearchResults());
      }
    }, 1000),
    [setSearchText, dispatch, setIsLoading],
  );
  return (
    <TextInput
      placeholder={'Search'}
      placeholderTextColor={placeholderColor}
      model={[styles.container, {backgroundColor, color}]}
      worth={searchText}
      onChangeText={(textual content: string) => {
        setSearchText(textual content);
        searchForText(textual content);
      }}
      maxLength={40}
      returnKeyType={'search'}
    />
  );
};

With that, we now have applied a search performance in our information feed software. The output will look one thing just like the gif beneath:

Searchnews Functionality Newsfeed

Including shared transition navigation

Our app is sort of full. We’re simply lacking one factor, the NewsDetail display.

As you will have seen within the output above, the shared transition impact is applied between two screens utilizing the react-navigation-shared-element.

First, let’s add all of the required dependencies to the challenge:

yarn add @react-navigation/native @react-navigation/stack react-native-safe-area-context react-native-screens

Now, implement the shared transition:

yarn add react-navigation-shared-element react-native-shared-element

To arrange navigation in our app, create a brand new file named navigation.tsx within the root of the challenge and add the next code to it:

import {NavigationContainer} from '@react-navigation/native';
import React from 'react';
import {createSharedElementStackNavigator} from 'react-navigation-shared-element';
import {Feed} from './src/screens/feed';
import {NewsDetails} from './src/screens/NewsDetails';

const Stack = createSharedElementStackNavigator();

export const RootNavigation = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName="Feed"
        screenOptions={{headerShown: false}}>
        <Stack.Display title="Feed" element={Feed} />
        <Stack.Display
          title="NewsDetails"
          element={NewsDetails}
          choices={{
            cardStyleInterpolator: ({present: {progress}}) => {
              return {
                cardStyle: {
                  opacity: progress,
                },
              };
            },
          }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

Discover that we’ve added two screens in our SharedElementsStackNavigator. The primary one is our default Feed display, and the opposite is the NewsDetails display, which we haven’t created but.

Let’s create the NewsDetails display and add shared components mapping to its parts. Create a brand new folder referred to as NewsDetails within the screens folder and add the next two recordsdata to it:

Newsdetails Screens Folder

Add the next code to the kinds.ts file:

import {StyleSheet} from 'react-native';

export default StyleSheet.create({
  container: {
    flex: 1,
  },
  contentContainer: {
    paddingBottom: 120,
  },
  picture: {
    top: 500,
    width: '100%',
    borderBottomLeftRadius: 50,
    borderBottomRightRadius: 50,
  },
  crossContainer: {
    place: 'absolute',
    prime: 60,
    left: 30,
    zIndex: 9,
  },
  cross: {
    top: 34,
    width: 34,
  },
  title: {
    fontSize: 24,
    fontWeight: '600',
    lineHeight: 30,
    paddingHorizontal: 24,
    marginVertical: 18,
  },
  content material: {
    fontSize: 16,
    fontWeight: '400',
    lineHeight: 22,
    paddingHorizontal: 24,
  },
  readMoreContainer: {
    place: 'absolute',
    paddingTop: 14,
    paddingBottom: 28,
    paddingHorizontal: 24,
    backside: 0,
    width: '100%',
  },
  readMoreText: {
    fontSize: 13,
    fontWeight: '300',
    lineHeight: 22,
  },
  hyperlink: {
    coloration: '#00beff',
    textDecorationLine: 'underline',
    textDecorationStyle: 'stable',
    textDecorationColor: '#00beff',
  },
});

To show the information particulars on the display, paste the code beneath in index.tsx:

import {useNavigation} from '@react-navigation/native';
import React, {useCallback} from 'react';
import {
  Picture,
  Linking,
  ScrollView,
  Textual content,
  TouchableOpacity,
  useColorScheme,
  View,
} from 'react-native';
import {SharedElement} from 'react-navigation-shared-element';
import {Again} from '../../../belongings';
import kinds from './kinds';

interface Route {
  params: {
    article: {
      writer: string;
      title: string;
      urlToImage: string;
      publishedAt: string;
      url: string;
      content material: string;
    };
    articleIndex: quantity;
  };
}
export const NewsDetails: React.FC<{route: Route}> = ({route}) => {
  const {article, articleIndex} = route?.params;
  const navigation = useNavigation();
  const goBack = useCallback(() => {
    navigation.goBack();
  }, [navigation]);
  const backgroundColor = useColorScheme() === 'darkish' ? '#000' : '#fff';
  const coloration = useColorScheme() === 'darkish' ? '#fff' : '#000';
  const contentColor = useColorScheme() === 'darkish' ? '#bbb' : '#444';
  const readMoreBgColor = useColorScheme() === 'darkish' ? '#222' : '#ddd';
  const handleURLPress = useCallback(() => {
    Linking.openURL(article?.url);
  }, [article]);

  return (
    <>
      <TouchableOpacity model={kinds.crossContainer} onPress={goBack}>
        <Picture model={kinds.cross} supply={Again} />
      </TouchableOpacity>
      <ScrollView
        bounces={false}
        showsVerticalScrollIndicator={false}
        model={[styles.container, {backgroundColor}]}
        contentContainerStyle={kinds.contentContainer}>
        <SharedElement id={`article#${articleIndex}-Picture`}>
          <Picture
            model={kinds.picture}
            supply={{uri: article?.urlToImage ?? 'https://picsum.photographs/1000'}}
            resizeMode={'cowl'}
          />
        </SharedElement>
        <Textual content model={[styles.title, {color}]}>{article?.title}</Textual content>
        <Textual content model={[styles.content, {color: contentColor}]}>
          {article?.content material}
        </Textual content>
      </ScrollView>
      <View
        model={[styles.readMoreContainer, {backgroundColor: readMoreBgColor}]}>
        <Textual content model={[styles.readMoreText, {color}]} numberOfLines={2}>
          Learn extra at{' '}
          <Textual content model={kinds.hyperlink} onPress={handleURLPress}>
            {article?.url}
          </Textual content>
        </Textual content>
      </View>
    </>
  );
};

(NewsDetails as any).sharedElements = (route: any) => {
  const {articleIndex} = route.params;
  return [`article#${articleIndex}-Image`];
};

We simply have to go the proper shared component mapping from the feed display. However, earlier than that, we have to render navigation as an alternative of the Feed element within the App.tsx file. Add the next code to App.tsx:

import React from 'react';
import {ActivityIndicator} from 'react-native';
import {Supplier} from 'react-redux';
import {PersistGate} from 'redux-persist/integration/react';
import {RootNavigation} from './Navigation';
import {persistor, retailer} from './src/redux/retailer';

const App: React.FC = () => {
  return (
    <Supplier retailer={retailer}>
      <PersistGate
        loading={<ActivityIndicator measurement={'giant'} coloration={'#000'} />}
        persistor={persistor}>
        <RootNavigation />
      </PersistGate>
    </Supplier>
  );
};
export default App;

Now, let’s transfer on to the NewsArticle element. Make the next adjustments within the NewsArticle/index.tsx file:

import {useNavigation} from '@react-navigation/native';
import second from 'second';
import React, {useCallback} from 'react';
import {Picture, Textual content, TouchableOpacity} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import {SharedElement} from 'react-navigation-shared-element';
import kinds from './kinds';

sort Publish = {
  title: string;
  urlToImage: string;
  publishedAt: string;
  url: string;
  writer: string;
};

export const NewsArticle: React.FC<{
  put up: Publish;
  index: quantity;
}> = ({put up, index}) => {
  const navigation: any = useNavigation();
  const handleNavigate = useCallback(() => {
    navigation.navigate('NewsDetails', {article: put up, articleIndex: index});
  }, [index, navigation, post]);
  return (
    <TouchableOpacity
      activeOpacity={1}
      model={kinds.container}
      onPress={handleNavigate}>
      <SharedElement
        model={kinds.imageContainer}
        id={`article#${index}-Picture`}>
        <Picture
          supply={{
            uri:
              put up?.urlToImage ??
              `https://picsum.photographs/${Math.flooring(Math.random() * 1000)}`,
            cache: 'force-cache',
          }}
          resizeMode={'cowl'}
          model={kinds.picture}
        />
      </SharedElement>
      <LinearGradient
        colours={['#0000', '#000A', '#000']}
        model={kinds.titleContainer}>
        <Textual content model={kinds.textual content}>{put up?.title}</Textual content>
        <Textual content model={kinds.timestamp}>
          {second(put up?.publishedAt).format('HH:MM DD, MMMM')}
        </Textual content>
      </LinearGradient>
    </TouchableOpacity>
  );
};

Within the code above, we wrapped the Picture in SharedElement and handed it an ID, which might be frequent throughout each screens. After implementing all of the respective adjustments, our app will appear like the next picture:

Final App UI Display

 

Conclusion

As you noticed from the tutorial, it’s pretty straightforward to create a information feed software utilizing React Native. This text was only a demo explaining how we will work together with REST API, however you possibly can go a lot additional, like utilizing WebSockets for real-time updates or utilizing the GraphQL API.

If you wish to make this app extra helpful, you possibly can add a webview to the app within the NewsDetail display, and when the consumer presses the hyperlink to learn extra concerning the article, you possibly can immediately open the hyperlink within the webview fairly than opening it in an exterior browser. I hope you loved this text, and pleased coding!

LogRocket: Immediately recreate points in your React Native apps.

LogRocket is a React Native monitoring answer that helps you reproduce points immediately, prioritize bugs, and perceive efficiency in your React Native apps.

LogRocket additionally helps you enhance conversion charges and product utilization by exhibiting you precisely how customers are interacting together with your app. LogRocket’s product analytics options floor the explanation why customers do not full a selected circulate or do not undertake a brand new characteristic.

Begin proactively monitoring your React Native apps — .

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments