Managing add to favorites and likes in reactnative

Saurabh Mhatre
CodeClassifiers
Published in
5 min readApr 18, 2017
Image source: Stocksnap

In one of my recent projects I had to handle add to favorites functionality to create personalized selection of topics for users.Initially I thought it was an easy process and could be finished in a few hours but it took a little while longer than my initial assumptions. My requirement was to add a simple heart button at the end of each topic in a grid on click of which the topic was supposed to be marked as favorite.

The simple demo of what we are trying to achieve today can be found in this video:

Technoetics

I used react-native-grid-view for generating grid and it was nested within a scrollview as follows:

import GridView from 'react-native-grid-view';
...
class GridViewClass extends Component {
render() {
return (
<ScrollView style={styles.gridScrollView} key={"scrollView"+this.props.parentKey+Math.random()}>
<GridView
key={"gridview"+this.props.parentKey}
items={this.props.gridViewData}
itemsPerRow={2}
renderItem={(item, sec, i) => this._renderRow(item, sec, i)}
style={styles.gridView}
enableEmptySections={true}
/>
</ScrollView>
);
}
}

We are passing json array to gridview as gridViewData prop in which every individual element has the following structure:

[
{
"topicName":"Quotes",
"title":"Strength",
"imageurl":"http://res.cloudinary.com/technoetics/image/upload/v1483988151/quotes/img1.jpg",
"story":"<p>“Strength does not come from winning. Your struggles develop your strengths. When you go through hardships and decide not to surrender, that is strength.” —<em>Arnold Schwarzenegger</em></p>",
"moral":"",
"sid":0
},
...
]

We define renderRow function which is responsible for rendering each individual item in Gridview as follows:

import Icon from "react-native-vector-icons/MaterialIcons";
...
class GridViewClass extends Component {
...
_renderRow(item, sec, i) {
let addToFavourites = true;
let iconColor = "white";
for(const i in this.props.favoriteTopics){
if(item.topicName === this.props.favoriteTopics[i].topicName){
addToFavourites = false;
iconColor = "red";
}
}
// console.log("item values",item.topicName,item.topicDescription);
return (
<View key={"gridItemContainer_"+item.topicName+this.props.parentKey} style={styles.gridItem}>
<View key={"viewGrid_" + item.topicName+this.props.parentKey} style={styles.gridContainer}>
<TouchableWithoutFeedback key={"touch_" + item.topicName+this.props.parentKey} onPress={() => this.props.openSubCat(item.topicId, item.topicName)}>
<View key={"touchableContainer"+item.topicId+this.props.parentKey}>
<ProgressiveImage imageKey={"imageGrid_" + item.topicName+this.props.parentKey} source={{ uri: item.imageurl }} resizeMode={'stretch'} thumbnailresizeMode={'cover'} thumbnail={require("../../images/placeholder.png")} style={{ width: 170, height: 90, padding: 4, alignSelf: 'center' }} />
<View key={"textContainer"+item.topicId+this.props.parentKey} style={styles.textcontainer}>
<Text style={styles.catname}>{item.topicName}</Text>
<Text style={styles.description}>{item.topicDescription}</Text>
</View>
</View>
</TouchableWithoutFeedback>
<Icon key={"iconKey"+item.topicName+this.props.parentKey} name="favorite" ref={(ref)=>this.storeRowRef(ref)} size={30} color={iconColor} onPress={(event) => addToFavourites?this.addToMyTopics(event,item.topicName):this.removeFromMyTopics(event,item.topicName)}/>
</View>
</View>
)
}
}

I have used favorite icon from material icons package provided in react-native-vector-icons module.

Here we set the value of addToFavourite value to true initially since we need to call addToFavourites function by default and iconColor as white since the topic is yet to be added to favorites and default icon color is set to white. We then check if the topic exists in favorites already and if it exists then we need to change icon color to red and mark removeFromFavourites option as active for given gridItem. All this actions are handled in color and onPress props by the Icon specified at the end of TouchableWithoutFeedback in return function.

Now we need to keep in mind that default refs provided by react native are insufficient to store and manipulate individual grid/listview items. We need to define our own ref storage array for this purpose as follows:

class GridViewClass extends Component {
constructor(props) {
super(props);
this.rowRefs = [];
this.storeRowRef = this.storeRowRef.bind(this);
this._renderRow = this._renderRow.bind(this);
}
storeRowRef(rowRef) {
this.rowRefs.push(rowRef);
}
_renderRow(item, sec, i) {
...
return (
...
<Icon ref={(ref)=>this.storeRowRef(ref)} size={30} color={iconColor} onPress={(event) => addToFavourites?this.addToMyTopics(event,item.topicName):this.removeFromMyTopics(event,item.topicName)}/>
...
)
}
}

In this case what we have essentially done is created a class level rowRefs array in constructor and a function to store individual rowRefs within that array called storeRowRef. Next on every renderRow item call we pass ref of each Icon item to storeRowRef function to store unique identifier for each item in rowRefs array.

We can manipulate the state of existing topics/gridItems using setNativeProps in componentDidMount as follows:

componentDidMount() {
this.rowRefs[0].setNativeProps({
style: {
color: 'blue',
}
})
}

In my case I have used redux to handle application state. Now on click of Icon element we can either add the topic into favorites or remove an existing topic from the array based on condition in renderRow function. We can define the addToFavorites/removefromFavorites function as follows:

addToMyTopics(event, topicName) {
// console.log("topic to be added",topicName,this.props.actions);
this.props.actions.addToFavourites(topicName);
}
removeFromMyTopics(event, topicName){
// console.log("topic to be removed",event,topicName);
this.props.actions.removeFromFavourites(topicName);
}

Here we are simply manipulating the state of favouriteTopics in redux store which in turn passed new data to gridview as follows:

We first use the above gridview Component in whichever pages we need:

class Catalogue extends Component {
constructor(props) {
super(props);
let favoriteTopicsData = [];
favoriteTopicsData.push(
this.props.topicNames
)
this.state = {
favoriteTopicsData: favoriteTopicsData
};
}
componentWillReceiveProps(nextProps){
// console.log("nextProps in myTopics",nextProps);
let favoriteTopicsData = [];
favoriteTopicsData.push(
this.props.topicNames
)
this.setState({favoriteTopicsData});
}
render() {
return (
<View key={"MyTopics"}>
<GridView gridViewData={this.state.favoriteTopicsData} openSubCat={this._openSubCat} parentKey={"MyTopics"} />
</View>
);
}
}

In my case I had two pages in which I have used gridview viz Catalog and MyTopics as shown in video in the beginning of this tutorial.As we can see the Gridview Component’s source data is a state variable called favoriteTopicsData which gets updated in componentWillReceiveProps and in turn triggers rerender of gridView. As we had seen in the previous section the addToFavorites and removeFromFavourites action is responsible for updating the state value of favoriteTopicsData by the means of reducer as follows:

export default function (state = favouriteTopics, action) {
switch (action.type) {
case types.ADD_TO_FAVOURITES:
let topicExists = false;
for(const i in state){
if(state[i].topicName == action.topicName){
topicExists = true;
break;
}
}
if(topicExists){
return state;
}
else{
return [
...state,
{
topicName: action.topicName,
}
]
}
case types.REMOVE_FROM_FAVOURITES:
return state.filter( (item, index) => item.topicName !== action.topicName);
default:
return state;
}
}

The flow is as follows:- Whenever someone clicks on heart icon, we dispatch addtofavourite action which in turn calls appropriate reducer. Now the job of reducer is to take previous favorite topics state, append the new topic from action in favouriteTopics array and update state. When state is updated the appropriate components receive new state as prop values from redux store. We take this new prop values in componentWillRecieveProps and update state of gridview input data source. This in turn triggers renderRow again and new gridview items are rerendered on page.

So this concludes today’s part of tutorial on creating add to favourites within a gridview/listview.

Bonus Tips:

In order to keep the tutorial simple I have not synced the favorite topics with the backend but in real case scenario you can store the topics in backend by calling appropriate http/api requests in reducers.

Since the number of topics were less I was okay with entire gridview being rerendered, however in production level applications make sure to update only individual grid items using efficient use of setNativeProps on refs array instead of updating state of entire gridview directly.

The same kind of system can be used to manage post likes, upvotes, comments or recommendations.

Connect Deeper:

In the next tutorial I will cover storing individual newfeed articles to store similar to save for later in facebook newsfeed. If you interested in getting notified about upcoming articles you can simply hit the like button on our facebook page: Technoetics

--

--