Creating swipable content cards in reactnative

reactnative

Whether you are creating your next social media app or news/media content based application there will always be some page which has a list of cards on click of which you need to display some content.

For example in the app that I am currently developing I had a list of questions displayed in listview like this:


cardlist

Lets call it as cardstack page for the rest of the tutorial.I have generated simple json using for loop in redux initialState:

for(var i=1;i<=10;i++){
questionanswers.push({
id:i,
question: i+". This is some test question for you which will not be useful in a few days but matters the most right now?",
answer:"This is some answer to the above question in the most absurd way possible because I will surely answer this question some day.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
hint:"This is some hint that is going to save you in a tough interview some day and hopefully will make all the efforts spent into making this app worthwhile"
})
}

And displayed questions in the card stack using Dynamiccard from nativebase module:

_renderRow(questionanswer){
var question = questionanswer.question;
var colorvalues=["#2196F3","#F44336","#009688","#9C27B0","#607D8B","#7C4DFF","#536DFE","#CDDC39","#FFEB3B","#03A9F4","#000000"];
var i = Math.floor(Math.random() * 11);
var colorvalue = colorvalues[i];
return(
<CardItem style={styles.cardItem} onPress={() => this.openQAPage(questionanswer)}>
<Icon name='ios-cube' style={[styles.questionIcon,{color:colorvalue}]} />
<Text style={styles.cardText}>{questionanswer.question}</Text>
{i==3 || i==5 ? <Icon name='ios-star' style={[styles.impquestionIcon,{alignSelf:'flex-end'}]} />:<View></View>}
</CardItem>
)
}
render() {
return (
this.state.isLoading ? <View style={styles.progressBar}><ProgressBar /></View> :
<Card dataArray={this.props.questionanswers}
renderRow={(questionanswer) => this._renderRow(questionanswer) }
</Card>
)
}

Now I have purposefully not used image in my cards since I felt that simple Icon on left might serve my purpose but more on that later.Now on click on one of the card the next screen that we need to display might be similar to something like this:


qacontainer

But once user has finished reading the content we are forcing the user to go back to previous screen and open the next card which can be avoided most of the times by providing on screen navigation or make the cards swipable so that principle of minimum clicks is achieved similar to something like this:

First we have added two buttons at the bottom for easier navigation.Now it is advisable to have navigational buttons at the bottom because we generally tend to use our phone with one hand using our thumb and buttons at the bottom of screen are easier to click.

Secondly we have made the cardcontainer swipable so that user can easily navigate between cards by flick of thumb.This not only increases user engagement but also can be used to reduce the bounce rate from the app.

Lets call it cardcontent page for the rest of the tutorial.

Now lets get back to code:

I am going to use snap-carousel module for this tutorial so install it by running the following command:

npm install --save react-native-snap-carousel

I am passing id of the question as prop from onclick handler of cards in cardstack page:

openQAPage(questionanswer){
var pagename = this.props.subitemname;
var id = questionanswer.id;
this.props.navigator.push({
title: pagename,
screen: "app.cardstackPage",
passProps: {
id
}
});
}

I am using react-native-navigation module for this app.I found it slightly better then other navigation modules/options available in reactnative.You can find relevant tutorial to set it up here.

Now lets us set up render method of cardstackpage:

import {
View,
Text,
Dimensions,
ScrollView
} from 'react-native';
const { width: viewportWidth, height: viewportHeight } = Dimensions.get('window');
export const sliderWidth = viewportWidth;
export const itemWidth = viewportWidth;
import Carousel from 'react-native-snap-carousel';
import {Button, Icon } from 'native-base';
....
render() {
return (
this.state.isLoading ? <View style={styles.progressBar}><ProgressBar /></View> :
<View>
<Carousel
ref={'carousel'}
items={this.props.questionanswers}
renderItem={this._renderItem}
sliderWidth={sliderWidth}
inactiveSlideScale={1}
inactiveSlideOpacity={1}
autoplay={false}
firstItem={(this.props.id-1)}
enableSnap={true}
snapOnAndroid={true} //to enable snapping on android
itemWidth={itemWidth}
slideStyle={styles.slide} />
<View style={styles.buttonContainer}>
<Button info style={styles.previousButton} onPress={()=> this.refs['carousel'].snapToPrev()}>
Previous
<Icon name="ios-arrow-back"/>
</Button>
<Button success iconRight style={styles.nextButton} onPress={()=> this.refs['carousel'].snapToNext()}>
Next
<Icon name="ios-arrow-forward"/>
</Button>
</View>
</View>
);
}

Here firstItem takes the id passed from previous page and displays it as first page in series of eleven cards.Setting enablesnap option will scroll to the center of the nearest/active item releasing the touch swipe.

The snapToPrev and snapToNext method in handlers of button clicks are provided by the module itself.

Now lets start creating renderItem method:

_renderItem (questionanswer, index) {
var colorvalues=["#303F9F","#1976D2","#0288D1","#00796B","#512DA8","#7B1FA2","#455A64","#388E3C","#388E3C","#388E3C","#000000"];
var i = Math.floor(Math.random() * 11);
var colorvalue = colorvalues[i];
        return (
<ScrollView ref={(scrollView) => { _scrollView = scrollView; }}
automaticallyAdjustContentInsets={true}
scrollEventThrottle={200}
showsVerticalScrollIndicator={true}
style={styles.qaContainer}
contentContainerStyle={[styles.contentContainer,{backgroundColor:colorvalue}]}>
<Text style={styles.questionText}>{questionanswer.question}</Text>
<Icon name="ios-chatbubbles" style={styles.answerSeparator}/>
<Text style={styles.answerText}>{questionanswer.answer}</Text>
<Icon name="ios-game-controller-b" style={styles.hintSeparator}/>
<Text style={styles.hintText}>Hint:{questionanswer.hint}</Text>
</ScrollView>
);
}

First I have used Scrollview to allow vertical scrolling of card content since most of the times we need to display long text content in case of newsfeed articles.Next I have assigned random colors to each card from material palette to have better ui appearance for the app.You can customize content within the scrollview based on your app use cases.

Here is the stylesheet I used for the app:

import { Dimensions,StyleSheet } from 'react-native';
const { width: viewportWidth, height: viewportHeight } = Dimensions.get('window');
export const sliderWidth = viewportWidth;
export const itemWidth = viewportWidth;
const styles = StyleSheet.create({
viewContainer: {
backgroundColor: '#ffffff',
flex: 1,
alignItems: 'center'
},
qaContainer:{
minHeight:(viewportHeight*3/4),
},
contentContainer:{
alignItems:'center',
minHeight:(viewportHeight*3/4)+30,
},
slide: {
flexDirection: 'column',
width: itemWidth,
},
buttonContainer:{
width:viewportWidth,
height:viewportHeight/12,
justifyContent:'center',
flexDirection:'row'
},
previousButton:{
alignSelf:'flex-start',
width:viewportWidth/2,
height:viewportHeight/12
},
nextButton:{
marginTop:0,
alignSelf:'flex-end',
width:viewportWidth/2,
height:viewportHeight/12
}
...
});

Final Thoughts:

My work was greatly simplified by using react-native-snap-carousel so kudos to its developers. You might want to use either swipable cards or provide navigation buttons at the bottom/center of the screen. I decided to keep both since I could not come up with a way to let the user know that the cardcontents are swipable. Any suggestions from you in this regards are most welcome. There are other modules like react-native-swiper, react-native-spring-carousel,looped carousel one of which might suits your app use cases.

Secondly I have used icons instead of images in cardstack page since I wanted my screen to render faster without waiting for images to get loaded.If you are using images in your cards then make sure that you either add a placeholder or better yet add progressive image loaders like react-native-image-progress so that user are not left with blank thumbnails especially on slow networks.

Thirdly I was feeling a little lazy so went away with using dynamic card component instead of nativebase instead of creating my own listview.In that case if your app has long list of articles/cards then consider using sglistview module instead of simple listview to optimize the memory footprint of the app.

Bonus Tip:

It is advisable to show some relevant content info within the navigational buttons so that user gets basic information about next or previous content.

You can view my other articles about reactnative,react and redux here.

Kindly upvote the article if you like it and share any kind of feedback in the comments.Stay tuned for my upcoming articles since I like to share technical stuff with others quite often.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.