Cet article est le dernier de la série visant à créer une simple application de découverte de série basé sur l’api The Movie Db. Dans les articles précédents nous avons créer l’ensemble des composants et ensuite mis en place la navigation.
Nous allons maintenant passer à la récupération des données grâce à l’api de The Movie Db.
Pour commencer rendez vous sur le site The Movie Db afin de créer un compte et de générer l’api key nécéssaire pour pouvoir utiliser l’api. Une fois votre api key récupérée créez un fichier api.js dans le dossier constants et ajoutez l’api key que vous avez obtenue.
api.js
export default apiKey = 'YOUR API KEY'
Ensuite nous allons passer à la création des types d’actions pour la récupération des données, ajoutez les dans le fichier ActionTypes.jsexport const FETCHSTART = ‘FETCHSTART’
export const FETCH_DONE = 'FETCH_DONE'
export const FETCH_FAILED = 'FETCH_FAILED'
Maintenant que nous avons définis nos types d’actions nous allons pouvoir créer les actions pour ce faire créer un fichier fetchAction.js dans le dossiers actions et ajoutez y le code suivant.
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 })
Ici nous avons créer la fonction fetchData qui est l’action qui sera appelée par le composant pour récupérer les données. Les fonctions _fetchStart, _fetchDone et _fetchFailed servirons à notifier le reducer de l’état de l’action.
Ensuite passons à la création du 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 fetchState
Pensez à ajouter le reducer fetchReducer au rootReducer.
import { combineReducers } from 'redux'
import navReducer from './navReducer'
import fecthReducer from './fetchReducer'
const rootReducer = combineReducers({
navReducer,
fetchReducer,
})
export default rootReducer
Nous allons maintenant passez à la création du container HomeContainer qui nous permettra de mapper l’état du reducer et les actions aux propriétés du composant SerieListView
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)
Ensuite dans l’article précédent nous avions créés le composant NavRoot qui permettait d’afficher les composants en fonction de la route sélectionnée. Pour faire simple lorsque qu’on demandait la route ‘home’ nous retournions le composant SerieListView. Il va donc falloir remplacer ce dernier par le composant HomeContainer. Remplacez le code suivant :
import SerieList from './SerieListView'
par
import HomeContainer from '../containers/HomeContainer'
et le code suivant :
case 'home':
return <SerieList showDetail={(serieItem) =>{this._handleNavigate(detailRoute(serieItem))} } />
par
case 'home':
return <HomeContainer showDetail={(serieItem) =>{this._handleNavigate(detailRoute(serieItem))} } />
Maintenant que nous renvoyons le bon composant et que la fonction de récupération des données est créée. Il va falloir indiquer à notre composant quand il va devoir récupérer les données. Dans notre cas nous allons le faire une fois que le composant est monté. Pour ce fair ajoutez le code suivants dans le fichier SerieListView.js
componentDidMount() {
this.props.actions.fetchData(this.props.nextPage);
}
A noter qu’ici la valeur de nextPage est 1 qui est la valeur de l’état initial du reducer. Ensuite des que les données auront été récupérées la propriété data du composant seras mise à jour mais pas la datasource de la listView. Pour que la datasource soit mise à jour nous allons rajouter le code suivant.
componentWillReceiveProps(nextProps) {
if(nextProps.fetchSuccess && !nextProps.isFetching){
this.setState({
dataSource: this.state.ds.cloneWithRows(nextProps.data)
});
}
}
La méthode componentWillReceiveProps est appelée dès qu’une propriété change. Avant de mettre à jour la datasource nos testons bien que le fetching soit terminé et qu’il soit réussi.
Nous avions ajouté dans le premier article la méthode _onEndReached() qui a pour but de pouvoir charger plus de résultats dès que la fin de la liste est atteinte. Maintenant il suffit de rajouter l’action fetchData de la même manière que lorsque le composant est monté.
_onEndReached(){
if(this.props.hasMoreResult){
this.props.actions.fetchData(this.props.nextPage);
}
}
Enfin afin de pouvoir visualiser si l’application est entrain de charger les données nous allons rajouter un ActivityIndicator qui s’affichera lorsque la propriété isFetching aura la valeur true. Ajouter la fonction suivante dans le composant SerieListView.
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>
);
}
}
Utiliser la fonction de la manière suivante sous la 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()
}
Voilà nous en avons terminé avec la récupération des données.
Vous pouvez retrouver le code source ici.