The Movie Db / React Native Partie 3 : Les données

05 mai 2017

Thibault MOCELLIN
The Movie Db / React Native Partie 3 : Les données

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.