12 apps in 12 weeks - App 3

6 min. read

Intro

When I start this app that was right after Christmas. I didn’t start before so I got a 2 days timeframe for finish it. The first thing I wasn’t sure about what to make too. I just think about putting my streamingAppRedux to mobile.

I already know the Twitch api so that make my life easier working with this API.

Exponent?

Exponent is an XDE who help you to wrote React-Native code faster. How? They put some of the more use third libraries already in the app. Like they said in their video this is like Rails for React-Native.

Make life easier cause no need to open Xcode and Android Studio and let you focus on building your app. Another plus is the fact then you can send Exponent link to your friend and they can play with your app without passing by a store. Just need to download the Exponent app in Android or IOS and open your link with.

I really encourage you to try it.

What did I learn?

So this app wasn’t really complicated. I just need to fetch some API endpoint and show them to the user. But because I never really touch something without try new stuff I build an infinite scroll “I never did it before in React-Native”.

Infinite Scroll

The first thing I create the fetch action with the offset link for the Twitch API.

1
2
3
4
5
6
7
8
9
export async function getOffsetGames(offset) {
let data;
try {
data = await fetchData(`games/top?offset=${offset}&`);
} catch (err) {
console.log(err);
}
return data;
}

Here you can see nothing really complicated. Just pass the offset number from the front-end that’s it.

The reducer who handle this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const INITIAL_STATE = {
isFetched: false,
games: null,
error: null
};
const gamesAll = (state = INITIAL_STATE, action) => {
switch (action.type) {
case `${FETCH_TOP_GAMES}_PENDING`:
return state;
case `${FETCH_TOP_GAMES}_FULFILLED`:
return { ...state,
isFetched: true,
games: action.payload
};
case `${FETCH_TOP_GAMES}_REJECTED`:
return { ...state,
isFetched: true,
error: action.payload
};
case `${FETCH_OFFSET_TOP_GAMES}_PENDING`:
return state;
case `${FETCH_OFFSET_TOP_GAMES}_FULFILLED`:
return { ...state,
isFetched: true,
games: [...state.games, ...action.payload]
};
case `${FETCH_OFFSET_TOP_GAMES}_REJECTED`:
return { ...state,
isFetched: true,
error: action.payload
};
case RESET_TOP_GAMES:
return INITIAL_STATE;
default:
return state;
}
};

Here I just contact the old game’s array with the new array coming from the API.

My TopGames component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
class TopGames extends Component {
state = {
dataSource: null,
offset: 0,
loading: false,
isActionButtonVisible: true
}
componentWillMount() {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.setState({
dataSource: ds.cloneWithRows(this.props.games)
});
}
componentWillReceiveProps(nextProps) {
this.setState({
loading: false,
dataSource: this.state.dataSource.cloneWithRows(nextProps.games)
});
}
_listViewOffset = 0
_onEndReached() {
if (this.props.strFilter) { return; }
this.setState({ offset: this.state.offset + 10, loading: true }, () => {
return this.props.paginateGames(this.state.offset + 1);
});
}
_renderFooter() {
return this.state.loading && (
<View style={{ marginVertical: 20 }}>
<ActivityIndicator size="large" color={Colors.tPurple} />
<Text style={styles.loadingText}>Loading...</Text>
</View>
);
}
_onScroll(e) {
// Simple fade-in / fade-out animation
const CustomLayoutLinear = {
duration: 100,
create: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity },
update: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity },
delete: { type: LayoutAnimation.Types.linear, property: LayoutAnimation.Properties.opacity }
};
// Check if the user is scrolling up or down by confronting the new scroll position with your own one
const currentOffset = e.nativeEvent.contentOffset.y;
const direction = (currentOffset > 0 && currentOffset > this._listViewOffset)
? 'down'
: 'up';
// If the user is scrolling down (and the action-button is still visible) hide it
const isActionButtonVisible = direction === 'up';
if (isActionButtonVisible !== this.state.isActionButtonVisible) {
LayoutAnimation.configureNext(CustomLayoutLinear);
this.setState({ isActionButtonVisible });
}
// Update your scroll position
this._listViewOffset = currentOffset;
}
// TODO: REFRESH CONTROL FOR RELOAD DATA
render() {
console.log(this.props);
return (
<View style={styles.root}>
<ListView
ref={ref => this.gamesList = ref}
onScroll={e => this._onScroll(e)}
contentContainerStyle={styles.list}
renderRow={row => <GameItem data={row} checkLiked={this.props.checkLiked} />}
enableEmptySections
automaticallyAdjustContentInsets={false}
dataSource={this.state.dataSource}
onEndReached={() => this._onEndReached()}
renderFooter={() => this._renderFooter()}
/>
<FabButton visible={this.state.isActionButtonVisible} />
<ButtonScrollTop
visible={!this.state.isActionButtonVisible}
onPress={() => this.gamesList.scrollTo({ y: 0 })}
/>
</View>
);
}
}

Here I found the method onEndReached in the React-Native API. With that I just make the function call my paginateGames function and that’s it. My action calls the API and fetch the data. FulFilled my reducer with new games and my ListView receive new props coming from my store. As you can see I have put a loading indicator for the footer so make a bit more UX for the user.

What is this other function in this component? This is a function who check if the user scrolls down or up. That let me show the gamesFollow button or the scroll top button. I just check for the contentOffset and let make that change on an event.

Last Word

I make use of Redux-Persist for the first time. I really like this library and gonna use it in the future for everything related to LocalStorage or AsyncStorage.

I’m planning to maybe add features in the future like Auth with Twitch and make the user life chat directly from this little app. But because of this challenge, I cannot really lose time on all the features I want.

Hope you enjoy this little post.

Library use