Creating an Avengers app using the Ionic API, React and Redux

The previous tutorial on the avengers app was quite cool and simple. However, applications are generally more complex.

Most of the time there are many concurrent Components manipulating the same information.

In order to manage all the information, we can use a Redux store to track the changes and have a single Source of Truth.

As usual, we install some libraries:

yarn add -S redux react-redux redux-devtools

The redux library is the core library, the react-redux one provide some tools to help us.

The redux-devtools one is useful for logging and analyzing the evolution of the store in a Chrome extension for example.

The Redux store is then created in the main.jsx file of our Ionic React application.

This part is quite tricky if you are not used to the React Redux combo so let's take it step by step:

import React , { Component } from "react" ; import ReactDOM from "react-dom" ; import AvengersComponent from "./app/AvengersComponent.jsx" ; import AvengersReducers from "./app/AvengersReducers.jsx" ; import { createStore } from "redux" ; import { Provider } from "react-redux" ; const store = createStore ( AvengersReducers , window . __REDUX_DEVTOOLS_EXTENSION__ && window . __REDUX_DEVTOOLS_EXTENSION__ ( ) ) ;

We import the createStore method from the redux library and create a new one that we stock in a store constant. In order to initialise the Redux store we use a Reducer named AvengersReducers (more on that later) and an option allowing us to use the Redux Devtools Extension.

We also grab a Provider from the react-redux library and use it like this:

ReactDOM . render ( < Provider store = { store } > < AvengersComponent / > < / Provider > , document . getElementById ( "root" ) ) ;

This Provider will allow us to access our Redux store in any React Component of our application. It's like a more sophisticated global variable.

Let's now take a look at the AvengersReducers.jsx file:

export const INITIAL_STATE = { avengersSerieList : [ ] } ; export default ( state = INITIAL_STATE , action ) = > { switch ( action . type ) { case "ADD_AVENGERS_SERIE" : return { avengersSerieList : [ . . . state . avengersSerieList , action . payload ] } ; default : return state } ; }

This function will be the only element allowing us to modify the data in the Redux store.

A simple INITIAL_STATE is used which contains an array property named avengersSerieList.

This function will only accept one type of action: the ADD_AVENGERS_SERIE one.

It will acquire the content in the Redux store of the current state's avengersSerieList and push the payload inside.

By default if there are no actions, we only return the current state.

Before diving in the modification of the AvengersComponent, let's take a look at the AvengersPresentationComponent.

This is where the presentation will be done. It's what we call a Dumb Component, it receives information and just display it in a certain way:

import React , { Component } from 'react' ; export default class AvengersPresentationComponent extends Component { constructor ( props ) { super ( props ) ; } render ( ) { const { avengersSerieList } = this . props ; return ( < div > { avengersSerieList . map ( ( serie , i ) = > { return < ion - card key = { i } > < ion - card - header > < ion - card - title > { serie . title } < / ion - card - title > < / ion - card - header > < ion - card - content > < ion - img src = { serie . img } / > < / ion - card - content > < / ion - card > } ) } < / div > ) ; } }

The information comes from the props and we use the ES6 destructuring assignment tool to grab the avengersSerieList property in the render method to finally use the Ionic API.

The rest of the work is done in the AvengersComponent.jsx file.

The initialisation doesn't change much:

import React , { Component } from 'react' ; import PropTypes from 'prop-types' ; import AvengersPresentationComponent from "./AvengersPresentationComponent.jsx" ; import { connect } from 'react-redux' import axios from 'axios' ; class AvengersComponent extends Component { constructor ( props , context ) { super ( props ) ; this . state = { } this . store = context . store ; this . apiKey = '?apikey=62fd825f7cda72881097e65913c376c5' ; this . size = '/standard_fantastic' ; }

We import the connect method from the react-redux library in order to link the state of the React Component to the Redux store.

We need PropTypes to acquire our Redux store.

The Redux store is available in the context of our Component. However, it will stay undefined unless we clearly define the context types like this:

AvengersComponent . contextTypes = { store : PropTypes . object } ;

Our next step is to modify the getSerieThumbnail method. In the previous tutorial we used the setState method, but now we must use the Redux store dispatch method:

async getSerieThumbnail ( serie ) { const response = await axios . get ( serie . resourceURI + this . apiKey ) . . . this . store . dispatch ( { type : "ADD_AVENGERS_SERIE" , payload : { img : img , title : title } } ) }

The store will receive the ADD_AVENGERS_SERIE action type with a payload containing the new serie to add then modify the store according to the previously created AvengersReducers method.

Once the store is updated, we need to sync our React Component with the Redux store.

. . . AvengersComponent . contextTypes = { store : PropTypes . object } ; const mapStateToProps = state = > { return { avengersSerieList : state . avengersSerieList } } export default connect ( mapStateToProps , ) ( AvengersComponent )

This is done by passing a mapStateToProps function to the react-redux connect method.

This function allows us to format the props that we want to receive from the store.

From here there are two ways to update the data in the AvengersComponent.

Either through the componentWillReceiveProps hook:

componentWillReceiveProps ( newProps ) { }

or directly in the render method:

render ( ) { return ( < div > < AvengersPresentationComponent avengersSerieList = { this . props . avengersSerieList } / > < / div > ) ; }

I prefer the second one since it's more compact.

The application should be now running like clockwork.