React Native + Redux passo a passo na prática

No sexto artigo da nossa série de artigos sobre React Native, vamos adicionar o Redux para gerenciar o estado do nosso aplicativo. Relembrando, estamos desenvolvendo um aplicativo que lista ofertas, promoções e desconto em produtos,  pegando essas informações de uma API. No artigo anterior Consumindo dados de uma API REST com React Native, nós vimos como consumir dados de uma API REST e os exibir no aplicativo. Neste artigo vamos ver como adicionar o Redux em nosso aplicativo React Native, para gerenciar o estado de nossa aplicação. Veja a aplicação já disponível em meu Git Hub para acompanhar melhor o artigo. https://github.com/andersonirias/react-native-tutorial.

O que é Redux?

O Redux é uma biblioteca JavaScript para gerenciar o estado de uma aplicação. O Redux é comumente utilizado com o React ou Angular, na construção de interfaces para o usuário. O estado de um aplicativo React são os dados do aplicativo que variam com sua utilização. Atualmente em nosso aplicativo, no componente PromotionsList que é o componente onde é realizado a busca das promoções e sua listagem, estamos utilizando o objeto state para armazenar o estado com qual este componente trabalha.

state = {
  data: []
}

Como vimos no artigo anterior, nosso estado possui um array data que inicia vazio. Assim toda vez que a aplicação é iniciada o estado está vazio. Utilizamos então a função componentDidMount que faz parte do ciclo de vida de um componente React para buscar as promoçṍes em nossa API REST e assim adicionar as promoções no array data de nosso estado.

 componentDidMount() {
    axios.get('https://irias.com.br/tutorials/react-native/api.php').then(response => { 
      this.setState({ data: response.data })
    }).catch(() => { 
      console.log('Error retrieving data')
    })
 }

A função acima utiliza o axios para consumir a API REST e utiliza a função setState  para adicionar a resposta da API ao objeto estado do componente.

Trabalhando deste modo nossa aplicação funciona corretamente, mas nossa aplicação contém mais de um componente que utilizará o mesmo objeto de estado. Atualmente somente o componente PromotionsList, utiliza o estado data onde se encontra a lista de promoções. Mas em breve o componente de busca também irá precisar manipular este estado, para conseguir atualizar a lista depois do resultado da busca. Para fazer algo do tipo podemos utilizar as props, que é um modo de passar valores entre os componentes da aplicação.

Em uma props o valor é passado de um componente pai para um componente filho. Temos um exemplo disso no próprio componente PromotionsList, ele é um componente pai do componente PromotionCard. O componente PromotionsList é a lista de promoções, cada promoção da lista é exibida em um card diferente que é o componente PromotionCard, assim os dados que serão exibidos no card são passados para o PromotionCard na props data.

 render() {
    return(
      <FlatList 
        style={ Styles.promotionsList }
        data={ this.state.data }
        renderItem={ ({ item }) => (
          <PromotionCard data={ item } />
        )}
        keyExtractor={ item => item.id } 
      />
    )
  }
}

Assim quanto maior o número de componentes que compartilham do mesmo estado na aplicação, mais complexo se torna o trabalho com estes dados entre os componentes, tanto sua exibição quanto sua atualização. E neste ponto que o Redux entra, com ele é possível deixar o estado centralizado e realizar sua manipulação de forma padronizada e clara sem correr riscos de gerar bugs alterando de forma incorreta o estado de nosso Aplicativo.

Agora após compreendermos um pouco sobre o Redux e sobre sua utilidade, vamos ver na prática como realizar sua implementação em nosso aplicativo. Primeiro passo vamos instalar o Redux em nosso aplicativo.

npm install --save redux

npm install --save react-redux

Após isto vamos começar criando nossas Actions. As Actions são eventos que ocorrem no aplicativo e disparam atualizações no estado do aplicativo. Elas descrevem os eventos que podem acontecer no aplicativo. Crie o diretório actions na raiz da aplicação e dentro dele crie o arquivo index.js. Nossas Actions ficarão do seguinte modo:

export const addPromotion = data => ({
  type: 'ADD_PROMOTION',
  data
})

export const clearPromotions = {
  type: 'CLEAR_PROMOTIONS'
}

export const pageIncrement = {
  type: 'INCREMENT'
}

export const pageBackToStart = {
  type: 'BACK_TO_START'
}

export const setRefreshTrue = {
  type: 'SET_REFRESH_TRUE'
}

export const setRefreshFalse = {
  type: 'SET_REFRESH_FALSE'
}

Acima criamos objetos que possuem a chave type, que é uma descrição do que a ação realiza. Na action addPromotion temos uma função que além do type, recebe também os dados que devem ser utilizados nesta ação em específico.

Após isto vamos criar nossos Reducers. Os Reducers são funções que utilizam o estado atual e a Action para retornar um novo estado. Temos a Action que descreve a ação e temos o estado atual do aplicativo, no Reducer combinamos a Action e o estado atual para gerar o novo estado do aplicativo.  Crie o diretório reducers na raiz da aplicação e dentro dele crie o arquivo os arquivos: index.js, promotionPage.js, promotions.js e promotionsRefresh.js.

Nosso reducer promotionPage.js ficará da seguinte maneira:

const promotionPage = (state = {page: 1}, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { page: state.page + 1 }
    case 'BACK_TO_START':
      return { page: 1 }
    default:
      return state
  }
}

export default promotionPage

Neste Reducer estamos falando que quando utilizarmos a ação do tipo increment, ele vai retornar o estado page como o estado page atual + 1, ou seja incrementando 1 ao valor de page atual. E na ação do tipo back_to_start ele retorna o estado page como 1 que é o estado inicial. Estas duas ações são referentes ao estado page, que é onde fica a página atual da listagem das promoções.

Nosso reducer promotions.js ficará da seguinte maneira:

const promotions = (state = [], action) => {
  switch (action.type) {
    case 'ADD_PROMOTION':
      return [
        ...state,
        {
          id: action.data.id,
          title: action.data.title,
          price: action.data.price,
          imageUri: action.data.imageUri,
          linkUrl: action.data.linkUrl
        }
      ]
    case 'CLEAR_PROMOTIONS':
      return state = []
    default:
      return state
  }
}

export default promotions

Neste Reducer estamos falando que ao utilizar a ação add_promotion, ele vai retornar o estado promotions atual acrescido de mais uma promoção que foi passada por parâmetro na ação. Já na ação clear_promotions ele vai retornar o estado promotions vazio que é seu estado atual.

Nosso reducer promotionsRefresh.js ficará da seguinte maneira:

const promotionsRefresh = (state = {refreshing: false}, action) => {
  switch (action.type) {
    case 'SET_REFRESH_TRUE':
      return { refreshing: true }
    case 'SET_REFRESH_FALSE':
      return { refreshing: false }
    default:
      return state
  }
}

export default promotionsRefresh

Neste Reducer na ação do tipo set_refresh_true retornamos o estado refresh como true. Já na ação set_refresh_false retornamos o estado refresh como false. Utilizamos este estado no componente de lista, na função de atualização da lista.

Nosso index.js ficará da seguinte maneira:

import { combineReducers } from 'redux'
import promotions from './promotions'
import promotionPage from './promotionPage'
import promotionsRefresh from './promotionsRefresh'

export default combineReducers({
  promotions,
  promotionPage,
  promotionsRefresh
})

Em nosso index utilizamos a função combineReducers para fazer a combinação de nossos três Reducers e exportar como um único Reducer.

Por último vamos criar nossa Store. A Store é onde estará todo o estado do nosso aplicativo. Ele é uma fonte única, onde ficará o estado que todos os componentes da aplicação irão utilizar. Crie o diretório store na raiz da aplicação e dentro dele crie o arquivo index.js. Nosso Store ficará do seguinte modo:

import { createStore } from 'redux'
import rootReducer from './../reducers/index'

const store = createStore(rootReducer)

export default store

Acima utilizamos a função createStore que é nativa do Redux passando nossos Reducers, para criar nossa Store.

Após criamos nosso Store, Actions e Reducers, vamos atualizar nosso componente PromotionsList para que ele utilize o Redux. Vamos também fazer uma atualização no modo de utilizar a API para buscar as promoções por página.

Nosso App.js ficará da seguinte maneira:

import React from 'react'
import { Provider } from 'react-redux'
import {
  SafeAreaView,
  StatusBar,
} from 'react-native'
import Header from './components/Header'
import PromotionsList from './components/PromotionsList'
import store from './store/'

class App extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <StatusBar />
        <SafeAreaView>
          <Header />
          <PromotionsList />
        </SafeAreaView>
      </Provider>
    )
  }
}

export default App

Vamos utilizar o componente Provider que é próprio do React Redux para ser o componente principal da aplicação. Vamos importar nossa Store e passar ela na props store do componente Provider. Isto será necessário para utilizar o Redux em todo nosso aplicativo.

Outro detalhe sobre nosso App.js, é que realizei a alteração do mesmo para o estilo de classes, para que fique padrão a utilização de classes em todo o aplicativo.

E por fim nosso  components/PromotionsList/index.js ficará da seguinte maneira:

import React, { Component } from 'react'
import { FlatList } from 'react-native'
import Styles from './styles'
import PromotionCard from './../PromotionCard'
import axios from 'axios'
import { connect } from 'react-redux'
import store from './../../store/'
import { 
  addPromotion, 
  pageIncrement, 
  pageBackToStart, 
  clearPromotions, 
  setRefreshTrue, 
  setRefreshFalse 
} from './../../actions'

class PromotionsList extends Component {
  constructor(props) {
    super(props)
  }
  
  request = () => {
    const page = store.getState().promotionPage.page
    axios.get('https://irias.com.br/tutorials/react-native/api.php?page=' + page).then(response => { 
      response.data.forEach(element => {
        this.props.dispatch(setRefreshFalse)
        this.props.dispatch(addPromotion(element))
      })
    })
  }

  componentDidMount() {
   this.request()
  }

  loadNewPage() {
    this.props.dispatch(pageIncrement)
    this.request()
  }

  refresh() {
    this.props.dispatch(setRefreshTrue)
    this.props.dispatch(clearPromotions)
    this.props.dispatch(pageBackToStart)
    this.request()
  }

  render() {
    return(
      <FlatList 
        style={ Styles.promotionsList }
        data={ this.props.promotions.data }
        renderItem={ ({ item }) => (
          <PromotionCard data={ item } />
        )}
        keyExtractor={ item => item.id }
        onEndReachedThreshold={ 0.01 }
        onEndReached={ () => {
          this.loadNewPage()
        }}
        refreshing={ this.props.promotions.refreshing }
        onRefresh={ () => {
          this.refresh()
        }}
      />
    )
  }
}

const mapStateToProps = (state) => {
  const  promotions = { 
    data: state.promotions,
    refreshing: state.promotionsRefresh.refreshing
  }
  return { promotions }
}

export default connect(mapStateToProps)(PromotionsList)

Este foi o componente que teve mais alterações, primeiro importamos o componente connect que é nativo do React Redux, importamos nossa store e também todas nossas actions. Em nossa classe utilizamos a função constructor, para utilizarmos nossas props. Aqui vamos receber o store com o estado atual da aplicação como uma props. Após isto temos as funções:

request – Nesta função utilizamos a função getState() do store que importamos. Esta função é própria da Store e nos permite pegar o estado atualizado. Utilizamos ela para pegar a página que foi recém atualizada. Depois utilizamos o axios para realizar a requisição na API REST. Ao receber a resposta da API, percorremos o array de resposta com a função forEach e utilizamos então a função dispatch que está disponível em nossa props para utilizar uma ação.

A função dispatch possibilita executar uma ação, que foi descrita na action em nossa Store. Nesta parte utilizamos a ação setRefreshFalse que é um objeto e utilizamos também a ação addPromotion() que é uma função. Na ação addPromotion passamos por parâmetro nossa promoção que foi recuperada pela API. Assim como descrito em nossa Action ele pega esses dados e adiciona ao estado promotions já existente, ele faz isto um por um enquanto o forEach percorre a resposta.

componentDidMount – Esta função é do ciclo de vida do componente React, ela sempre é executada após o componente React ser montado. Nesta função estamos somente chamado a função request para buscar as promoções.

loadNewPage – Esta função é chamada toda vez que o usuário chega ao final da lista de promoções na tela do aplicativo. Quando ele chega no fim esta função utiliza o dispatch, para para executar a ação pageIncrement em nossa Store. Assim ele muda a consulta na API para a próxima página. Depois disso ele chama a função request para buscar as promoções da página atual e adicionar as promoções já existentes.

refresh – Esta função é chamada toda vez que o usuário puxa para baixo a lista, assim atualizando ela. Nela utilizamos o dispatch para executar três ações em nossa Store:

setRefreshTrue – Esta ação fala que a lista de promoções está em estado de atualização.

clearPrmomotions – Esta ação limpa a lista de promoções.

pageBackToStart – Esta ação volta a API para a página 1 que é a página inicial.

Depois disso ele chama a função request para buscar as promoções da página atual que é a página 1 e adicionar a lista de promoções que está vazia.

render – No artigo: Como criar uma lista de produtos com React Native,  eu explico tudo que é utilizado nesta função.

mapStateToProps – Nesta variável que recebe uma função, fazemos um mapeamento do estado recebido na props para que possamos utilizar o mesmo em nosso componente PromotionsList.

Por último fazemos o export do componente PromotionsList é utilizamos o connect qual fizemos o import do React Redux, para fazer a conexão do componente react com a Store Redux. Com isso nosso componente pode utilizar por meio das props as funções da Store .

Ao finalizar este artigo a aparência do nosso aplicativo não mudará, mas repare bem que ele lista as promoções até certo ponto e depois que chega ao final ele busca mais promoções para adicionar à lista. Também ao atualizar o aplicativo, ele realiza uma nova busca na API trazendo os resultados atuais.

Assim concluímos mais uma etapa do nosso projeto que já está quase chegando ao fim.

O projeto está disponível em meu GitHub:  https://github.com/andersonirias/react-native-tutorial.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *