The Movie Db / React Native Partie 2 : La navigation

02 mai 2017

Thibault MOCELLIN
The Movie Db / React Native Partie 2 : La navigation

Cet article est le deuxième de la série visant à créer une simple application de découverte de série basé sur l'api The Movie Db. Dans l'article précédent nous avons créés l'ensemble des composants. Nous allons maintenant passer à la navigation.

Pour la navigation nous allons utiliser le composant NavigationExperimental et Redux. Si vous voulez en savoir plus sur les différents composants de navigation vous pouvez consulter [le guide sur la navigation(https://facebook.github.io/react-native/docs/navigation.html)].

Nous partirons du principe que vous connaissez déjà Redux, sinon commencez par regardez la documentation de Redux.

Commençons par ajouter Redux à nôtre projet :

yarn add redux react-redux redux-thunk redux-logger

Ensuite créez les dossiers suivants dans le dossier src

mkdir reducers store actions routes containers constants

Maintenant que nous avons installer Redux et que la structure des dossiers est prête nous allons définir les deux types d'actions que nous utiliserons pour la navigation. Dans le dossier constants créez le fichier ActionTypes.js et ajoutez le code suivant :

ActionTypes.js

export const PUSH = 'PUSH'
export const POP = 'POP'

Passons à la création des actions, dans le dossier actions créez le fichier navActions.js et ajoutez le code suivant :

navActions.js

import { POP, PUSH } from '../constants/ActionTypes'

export function push(route) {
  return {
    type: PUSH,
    route,
  }
}

export function pop() {
  return {
    type: POP,
  }
}

Nous avons donc deux actions :

  • pop : L'action qui permettras de revenir à l'écran précédent quand nous serons sur la fiche d'une série.
  • push : L'action qui permet de naviguer jusqu'à la route passé en paramètre (dans notre cas la route de la fiche d'une série)

Dans la foulé nous allons directement créer la route qui nous permettras d'afficher la fiche d'une série.

Dans le dossier routes créez un fichier index.js et ajoutez le code suivants :

index.js

export const detailRoute = item => {
  return {
    type: 'push',
    route: {
      key: 'detail',
      title: 'Detail',
      serieItem: item,
    },
  }
}

Cet objet route contient différentes informations :

  • type : Le type d'action de la route
  • key : Permet de définir quel composant devra être rendu
  • title : Titre de la vue qui sera rendue
  • serieItem : Les données de la série affichée

Ensuite nous allons créer notre reducer pour la navigation dans le dossier reducers.

navReducer.js

import { PUSH, POP } from '../constants/ActionTypes'
import { NavigationExperimental } from 'react-native'
const { StateUtils: StateUtils } = NavigationExperimental

const initialState = {
  index: 0,
  key: 'root',
  routes: [
    {
      key: 'home',
      title: 'Series',
    },
  ],
}

function navigationState(state = initialState, action) {
  switch (action.type) {
    case PUSH:
      if (state.routes[state.index].key === (action.route && action.route.key))
        return state
      return StateUtils.push(state, action.route)

    case POP:
      if (state.index === 0 || state.routes.length === 1) return state
      return StateUtils.pop(state)

    default:
      return state
  }
}

export default navigationState

Ici nous commençons par importer nos types d'actions précédemment créés, ensuite nous importons NavigiationExperimental et définissons StateUtils. StateUtils est un helper qui va nous permettre d'appeler les méthodes basiques de routing. Enfin nous définissions l'objet initialState, dans cet objet il y à la route par défault depuis laquelle nous allons partir.

Créons maitenant notre rootReducer toujours dans le dossier reducers ajouter le fichier index.js

index.js

import { combineReducers } from 'redux'
import navReducer from './navReducer'

const rootReducer = combineReducers({
  navReducer,
})

export default rootReducer

Nous pouvons maintenant créer notre store redux,ajouter le fichier suivant dans le dossier store.

configureStore.js

import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import rootReducer from '../reducers'

const loggerMiddleware = createLogger()

export default function configureStore() {
  const middleware = [thunkMiddleware, loggerMiddleware]
  let store = compose(applyMiddleware(...middleware))(createStore)(rootReducer)
  return store
}

Maintenant nous pouvons passer à la création du composant de navigation.

Créons le composant navRoot qui sera le point d'entrée de l'application et qui sera rendu lors du chargement de l'application.

navRoot.js

import React, { Component } from 'react'
import { View, StatusBar } from 'react-native'
import SerieList from './SerieListView'
import SerieDetail from './SerieDetail'
import { detailRoute } from '../routes'

import { BackAndroid, NavigationExperimental } from 'react-native'

const { CardStack: NavigationCardStack } = NavigationExperimental

class NavRoot extends Component {
  constructor(props) {
    super(props)
    this._renderScene = this._renderScene.bind(this)
    this._handleBackAction = this._handleBackAction.bind(this)
  }
  componentDidMount() {
    BackAndroid.addEventListener('hardwareBackPress', this._handleBackAction)
  }
  componentWillUnmount() {
    BackAndroid.removeEventListener('hardwareBackPress', this._handleBackAction)
  }
  _renderScene(props) {
    const { route } = props.scene
    switch (route.key) {
      case 'home':
        return (
          <SerieList
            showDetail={serieItem => {
              this._handleNavigate(detailRoute(serieItem))
            }}
          />
        )
        break
      case 'detail':
        return (
          <SerieDetail
            goBack={() => this._handleBackAction()}
            serieItem={route.serieItem}
          />
        )
        break
    }
  }
  _handleBackAction() {
    if (this.props.navigation.index === 0) {
      return false
    }
    this.props.popRoute()
    return true
  }
  _handleNavigate(action) {
    switch (action && action.type) {
      case 'push':
        this.props.pushRoute(action.route)
        return true
      case 'back':
      case 'pop':
        return this._handleBackAction()
      default:
        return false
    }
  }
  render() {
    return (
      <View style={{ flex: 1 }}>
        <StatusBar backgroundColor={'black'} barStyle="light-content" />
        <NavigationCardStack
          direction="vertical"
          navigationState={this.props.navigation}
          onNavigate={this._handleNavigate.bind(this)}
          onNavigateBack={this._handleBackAction.bind(this)}
          renderScene={this._renderScene}
        />
      </View>
    )
  }
}
export default NavRoot

Regardons en détail ce qui se passe dans ce composant

  1. Nous importons nos composants ainsi que la route qui permet d'afficher la fiche d'une série
  2. Nous importons BackAndroid and NavigationExperimental depuis react-native. BackAndroid nous permettras de gérer le boutton de retour sur android
  3. On définit les callback pour l'évenement backAndroid
  4. On définit quel composant retourné en fonction de la route dans la méthode renderScene
  5. On passe aux composants les méthodes de navigation nécéssaires

Ensuite dans le dossier containers créons le composants NavRootContainer

NavRootContainer.js

import { connect } from 'react-redux'
import NavigationRoot from '../components/navRoot'
import { push, pop } from '../actions/navActions'

function mapStateToProps(state) {
  return {
    navigation: state.navReducer,
  }
}

export default connect(mapStateToProps, {
  pushRoute: route => push(route),
  popRoute: () => pop(),
})(NavigationRoot)

Enfin pour terminer remplacer le code du fichier index.js à la racine du dossier src par le code suivants :

'use strict'
import React, { Component } from 'react'
import configureStore from './store/configureStore'
const store = configureStore()

import NavigationRootContainer from './containers/NavRootContainer'
import { Provider } from 'react-redux'

export default class App extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <Provider store={store}>
        <NavigationRootContainer />
      </Provider>
    )
  }
}

Voilà nous en avons terminer avec la partie sur la navigation.

Vous pouvez retrouver le code source ici.