This article is the last in the series for creating a simple app for discovering TV series based on The Movie DB API. In the previous articles, we created all the components and then set up navigation.
We will now move on to fetching data through The Movie DB API.
To start, go to the The Movie DB website to create an account and generate the API key required to use the API. Once you have retrieved your API key, create an api.js file in the constants folder and add the API key you obtained.
api.js
export default apiKey = 'YOUR API KEY'Next, we will create the action types for fetching data. Add them to the ActionTypes.js file:
export const FETCH_START = 'FETCH_START'
export const FETCH_DONE = 'FETCH_DONE'
export const FETCH_FAILED = 'FETCH_FAILED'Now that we have defined our action types, we can create the actions. To do this, create a fetchAction.js file in the actions folder and add the following code.
fecthActions.js
import { FETCH_START, FETCH_DONE, FETCH_FAILED } from '../constants/ActionTypes'
import apiKey from '../constants/api'
export function fetchData(page) {
return async dispatch => {
dispatch(_fetchStart())
try {
var path =
'https://api.themoviedb.org/3/discover/tv?include_null_first_air_dates=false&timezone=America%2FNew_York&page=' +
page +
'&sort_by=popularity.desc&language=en-US&api_key=' +
apiKey
var response = await fetch(path)
var data = await response.json()
data = {
hasMoreResult: page < data.total_pages,
series: data.results,
nextPage: page < data.total_pages ? page + 1 : page,
}
dispatch(_fetchDone(data))
} catch (error) {
dispatch(_fetchDone(error.message))
}
}
}
const _fetchStart = () => ({ type: FETCH_START })
const _fetchDone = data => ({ type: FETCH_DONE, data })
const _fetchFailed = msg => ({ type: FETCH_FAILED, msg })Here, we created the fetchData function, which is the action called by the component to fetch the data. The _fetchStart, _fetchDone, and _fetchFailed functions will be used to notify the reducer of the action state.
Next, let's create the reducer
fetchReducer.js
import { FETCH_START, FETCH_DONE, FETCH_FAILED } from '../constants/ActionTypes'
const initialState = {
series: [],
nextPage: 1,
hasMoreResult: true,
fetchSuccess: false,
isFetching: false,
errorMsg: '',
}
function fetchState(state = initialState, action) {
switch (action.type) {
case FETCH_START:
return { ...state, isFetching: true }
case FETCH_DONE:
return {
...state,
fetchSuccess: true,
isFetching: false,
hasMoreResult: action.data.hasMoreResult,
nextPage: action.data.nextPage,
series: [...state.series, ...action.data.series],
}
case FETCH_FAILED:
return {
...state,
fetchSuccess: false,
isFetching: false,
errorMsg: action.msg,
}
default:
return state
}
}
export default fetchStateRemember to add the fetchReducer reducer to the rootReducer.
import { combineReducers } from 'redux'
import navReducer from './navReducer'
import fecthReducer from './fetchReducer'
const rootReducer = combineReducers({
navReducer,
fetchReducer,
})
export default rootReducerWe will now create the HomeContainer container, which lets us map the reducer state and actions to the properties of the SerieListView component.
HomeContainer.js
import { connect } from 'react-redux'
import SerieListView from '../components/SerieListView'
import { bindActionCreators } from 'redux'
import * as fetchActions from '../actions/fetchActions'
function mapStateToProps(state, ownProps) {
return {
nextPage: state.fetchReducer.nextPage,
hasMoreResult: state.fetchReducer.hasMoreResult,
fetchSuccess: state.fetchReducer.fetchSuccess,
isFetching: state.fetchReducer.isFetching,
errorMsg: state.fetchReducer.errorMsg,
showDetail: ownProps.showDetail,
data: state.fetchReducer.series,
}
}
export default connect(mapStateToProps, dispatch => ({
actions: bindActionCreators(fetchActions, dispatch),
}))(SerieListView)Next, in the previous article, we created the NavRoot component, which displayed components based on the selected route. To keep things simple, when the 'home' route was requested, we returned the SerieListView component. We will now need to replace it with the HomeContainer component. Replace the following code:
import SerieList from './SerieListView'with
import HomeContainer from '../containers/HomeContainer'and the following code:
case 'home':
return <SerieList showDetail={(serieItem) =>{this._handleNavigate(detailRoute(serieItem))} } />with
case 'home':
return <HomeContainer showDetail={(serieItem) =>{this._handleNavigate(detailRoute(serieItem))} } />Now that we return the right component and the data-fetching function has been created, we need to tell our component when it should fetch the data. In our case, we will do this once the component is mounted. To do this, add the following code in the SerieListView.js file:
componentDidMount() {
this.props.actions.fetchData(this.props.nextPage);
}Note that here the value of nextPage is 1, which is the reducer's initial state value. Then, as soon as the data has been fetched, the component's data property will be updated, but not the listView data source. To update the data source, we will add the following code.
componentWillReceiveProps(nextProps) {
if(nextProps.fetchSuccess && !nextProps.isFetching){
this.setState({
dataSource: this.state.ds.cloneWithRows(nextProps.data)
});
}
}The componentWillReceiveProps method is called as soon as a property changes. Before updating the data source, we make sure that fetching has finished and succeeded.
In the first article, we added the _onEndReached() method, whose purpose is to load more results as soon as the end of the list is reached. Now we simply need to add the fetchData action in the same way as when the component is mounted.
_onEndReached(){
if(this.props.hasMoreResult){
this.props.actions.fetchData(this.props.nextPage);
}
}Finally, so we can see whether the app is currently loading data, we will add an ActivityIndicator that displays when the isFetching property is true. Add the following function in the SerieListView component.
renderLoader(){
if(this.props.isFetching){
return(
<View style={{position:'absolute',bottom:0,left:0,right:0,justifyContent: 'center',alignItems: 'center'}}>
<ActivityIndicator animating={true} color='red' size='large' />
</View>
);
}
}Use the function as follows below the listView.
;<ListView
style={{ backgroundColor: '#706666' }}
enableEmptySections={true}
onEndReached={() => this._onEndReached()}
onEndReachedThreshold={10}
onLayout={event => {
this._onLayout(event)
}}
dataSource={this.state.dataSource}
renderRow={rowData => (
<ListViewItem
onItemPress={() => this.props.showDetail(rowData)}
title={rowData.original_name}
description={rowData.overview}
image={'https://image.tmdb.org/t/p/w500/' + rowData.poster_path}
height={this.state.height}
width={this.state.width}
/>
)}
/>
{
this.renderLoader()
}That's it: we are done with fetching data.