Aceder ao Firebase em React + Redux sem utilizar o mixin ReactFire

Aceder ao Firebase em React + Redux sem utilizar o mixin ReactFire

A base de dados Firebase disponibiliza um mixin React que nos permite manter actualizado os dados da nossa aplicação.

Um potencial problema é que os mixins são neste momento desaconselhados pela equipa do React.

Uma alternativa é a utilização do Redux. Para além das vantagens de se utilizar o Redux, nomeadamente a possibilidade de concentrar todo a lógica da aplicação, podemos ainda, neste caso, utilizar apenas o firebase.js e esquecermo-nos do reactfire.js.

A primeira coisa a fazer é instalar o firebase, por exemplo:

  npm install firebase

A seguir termos de inicializar a app firebase como indicado no guia ReactFire. Por facilidade de utilização, neste caso a inicialização foi feita no ficheiro que vai carregar o componente React.

Vamos agora construir um pequeno exemplo baseado nos dados do ReactFire. Para facilidade de desenvolvimento vamos começar pelo fim (pelo componente que vai usar os dados Firebase).

/**
 * TestPage.jsx
 */
import React, { Component } from 'react';  
import { connect } from 'react-redux';  
import { asyncFetchFirebaseData, closeFirebase } from '../../../actions/DataActions';

class TestPage extends Component {  
  componentWillMount() {
    this.props.dispatch(asyncFetchData());
  }

  componentWillUnmount() {
    this.props.dispatch(closeFirebase());
  }

  render() {
    const data = this.props.testData.map((value) => {
      return <span className={ `value-${value.id}` }>{ value.name }<span>;
    });

    return (
      <div className="values-wrapper">
        { data }
      </div>
    );
  }
}

function select(state) {  
  return {
    testData: state.TestReducer.TestInfo,
  };
}

export default connect(select)(TestPage);  

Aqui em cima temos um componente Redux standard que recebe uma lista e apresenta os vários elementos dessa lista em spans com o id a ser utilizado para o nome da classe e o name como valor visível.

O que estamos aqui a fazer é passar a lógica de pedido dos valores ao Firebase da função componentWillMount conforme indicado no guia do ReactFire para uma acção Redux.

Acção Redux

import firebase from 'firebase';

import { FETCH_DATA } from '../constants/TestConstants';

let firebaseRef;

export function closeFirebase() {  
  firebaseRef.off();
}

export function asyncFetchData() {  
  return (dispatch) => {
    firebaseRef = firebase.database().ref('testPath');
    firebaseRef.on('value', function(dataSnapshot) {
      var items = [];
      dataSnapshot.forEach(function(childSnapshot) {
        const item = childSnapshot.val();
        items.push(item);
      });

      return dispatch(fetchData(items));
    });
  };
}

export function fetchData(items) {  
  return { type: FETCH_DATA, items };
}

Este exemplo também consiste numa ação Redux normal. O destaque vai para a importação do firebase.js e para a referência firebaseRef. Esta referência existe aqui para podermos utilizá-la ao longo do ficheiro: para pedirmos os dados, fechar a ligação e acrescentar valores (embora não vá apresentar nenhum exemplo para este caso).

O ficheiro da constante também é bastante simples:

  export const FETCH_DATA = 'FETCH_DATA';

A forma como os valores do Firebase são compilados é o apresentado no guia do ReactFire.

O passo seguinte consiste em criar o reductor que também será um reductor Redux standard.

import { FETCH_DATA } from '../constants/TestConstants';

/**
 * Função para criar um novo objecto.
 */
import assignToEmpty from '../utils/assign';

const initialState = {  
  items: [],
};

function guestReducer(state = initialState, action) {  
  Object.freeze(state);
  switch (action.type) {
    case FETCH_DATA:
      return assignToEmpty(state, {
        items: action.items,
      })
    default:
      return state;
  }
}

export default guestReducer;  

E aqui fechamos o círculo: os valores deste redutor vão ser injectados no componente. Sempre que houver uma alteração no Firebase, os valores serão actualizados no componente.

Este exemplo é simples, mas será fácil implementar novas funcionalidades como a criação ou remoção de items usando uma estrutura semelhante.