Creating cards list(articles feed) in react native

Image Source:Pixabay

As an application developer we often need to display information in card views for mobile apps related to news,media and social apps.In one my recent projects I had to display around 300–400 cards on a certain topic in vertical scrollable list.Rendering them all at once would have terribly slowed down my application.So I created a listview to render 10 cards at a time,with a spinner until next ones are loaded.Here is a demo of what I created in the end:

I am using react-native-navigation-module from wix for handling navigation needs.You can find tutorial on setting it up here or download example app directly from here.

The steps are as follows:

Create a new component/class in your reactnative application.In my case I have created TopicsList Class:

import React, { Component, PropTypes } from 'react';
import {
View,
Text,
Image,
ListView,
StyleSheet,
Dimensions,
} from 'react-native';
import { Navigation } from 'react-native-navigation';
import ProgressBar from '../global/ProgressBar';
const { width: viewportWidth, height: viewportHeight } = Dimensions.get('window');
class TopicsLIst extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
}
}
componentDidMount() {
var self = this;
setTimeout(function () {
self.setState({ isLoading: false });
}, 1);
}
render() {
return (
this.state.isLoading ? <View style={styles.progressBar}><ProgressBar /></View> :
<View style={styles.listContainer}>
</View>
);
}
}

I have used setTimeout in componentDidMount to simply add a delay which might happen until data is loaded from remote api in real use case scenarios.

I am using a simple activity indicator in ProgressBar component to display loading spinner until app is loaded:

import React from 'react';
import {
View,
ActivityIndicator,
StyleSheet
} from 'react-native';
const ProgressBar = () => (
<View style={styles.progressBar}>
<ActivityIndicator size="large" color="#EA0000" />
</View>
);
const styles = StyleSheet.create({
progressBar: {
flex: 1,
justifyContent: 'center'
}
});
export default ProgressBar;

Now we are going to use cards from nativebase module and SGListview module to render list which is a is a memory minded implementation of the React Native’s ListView.Install them by running the following command:

npm install --save native-base react-native-sglistview
react-native link

Now I have passed a json array as prop using redux to this component which contains cards related data as follows:

this.props.stories=[
{
"topicName":"sometopic",
"title":"sometitle",
"imageurl":imageurl",
"story":"storydata",
"moral":"",
"sid":0
},
...
]

We will first create a SGListView in the render method and initialize datasource as the above props in constructor as follows:

class TopicsLIst extends Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.state = {
isLoading: true,
dataSource: ds.cloneWithRows(this.props.stories),
}

}
....
render() {
return (
this.state.isLoading ? <View style={styles.progressBar}><ProgressBar /></View> :
<View style={styles.listContainer}>
<SGListView
dataSource={this.state.dataSource}
renderRow={this.renderCards}
pageSize={10}
stickyHeaderIndices={[]}
onEndReachedThreshold={1}
initialListSize={10}
renderFooter={(event)=>this.renderLoader(event)}
scrollRenderAheadDistance={4}
onEndReached={(event)=>this.hideLoader(event)}
/>
</View>
);
}
}

In the constructor method we have initialized the data source with our json array prop and initialized SGListview in render with following parameters:

  • dataSource: This is the input data that listview needs to render items
  • renderRow: This parameter is responsible for rendering each item in list.We will define our cardview in renderCards method next
  • pageSize:This parameter defines no of items to render per event loop.Simply speaking this parameter is useful to display 10 items at a time from several items and then progressively load next 10 items when we reach at the end of first page
  • stickyheaderIndices: This parameter can be used if you want any of the items in list to stick at the top when scrolling. It is a mandatory parameter in SGListView so keep it blank if you don’t need it
  • onEndReachedThreshold: Specifies no of pixels for calling onEndReached
  • initialListSize: No of items to be displayed in initial render
  • renderFooter:We can specify some views to be displayed at the end of the list. We have used this method to show spinner at the end of the each page in list in renderLoader
  • scrollRenderAheadDistance: Specifies how early to start rendering rows before they come on screen, in pixels.
  • onEndReached: We can specify some tasks to be done when end of list is hit.We have used this to hide loader/footer once all items are loaded.

Let us start with renderCards function.We need to import some components from native base as follows:

import { Container, Content, Card, CardItem, Left,Right, Body, Thumbnail,Spinner, Icon } from 'native-base';
import ProgressiveImage from '../global/progressiveImage';
...
class TopicsLIst extends Component {
...
renderCards(stories) {
return (
<Card>
<CardItem>
<Left style={{flex:0.8}}>
<Icon name="ios-book" />
<Body>
<Text style={{fontWeight:'bold',fontSize:17}}>{stories.title}</Text>
<Text note style={{fontSize:15}}>{stories.topicName}</Text>
</Body>
</Left>
<Right style={{flex:0.2}}>
<Icon name="ios-heart"/>
</Right>
</CardItem>
<CardItem cardBody>
<ProgressiveImage source={{ uri: stories.imageurl }} thumbnail={require("../../images/placeholder.png")} style={{ width: viewportWidth, height: 170 }} thumbnailresizeMode={"cover"} thumbnailkey={"pimg"} />
</CardItem>
<CardItem content>
<Text>{stories.story}</Text>
</CardItem>
</Card>
)
}
...
}

By default the renderCard function recieves each list item as input parameter from listview renderRow function.We have created simple card view with title,image container and related text.Now I found a few days back that the default Image component does not have offline/loading placeholder capability in android so I created custom progressiveimage component for the purpose.You can find the tutorial to set it up here.

Next let us cover renderLoader and hideLoader function which shows spinner at the end of page and remove it once data is loaded respectively.Now you have the option to either use spinner from nativebase or progressbar component we created earlier:

constructor(props) {
super(props);
this.state = {
...
showLoader:true
}
}
renderLoader(){
return (
this.state.showLoader?<View><Spinner color='red' /></View>:null
)
}
hideLoader(){
setTimeout(() => {
this.setState({showLoader:false})
}, 1);
}

The spinner is shown on the basis of showLoader flag which is set to true initially and reset to false in hideLoader. In ideal cases we will reset it after getting data from some api and I have used setTimeOut to simply simulate delay in getting data.

Note:I have used setTimeout in some places just to keep the tutorial simple but it should be avoided as far as possible when developing production level applications.

Connect Deeper: In the next tutorial I plan to cover click event of card items along with marking favorite functionality(heart icon click) in cards so follow our facebook page to get notified about the article in future: Technoetics or simply follow me back on medium.