简体   繁体   中英

Make React Redux Firebase asynchron

Hello all !

I'm facing some issues with redux and firebase . Is it mandatory to put the routers to the root components of the projects?

Some of mine work very well with this system, but not the asynchronous functions, mainly those of firebase .

This post is basically about how and where to place firebase.[objects] so that they are always read correctly. Official documentation is not very explicit and clear.

I remind you I'm new to firebase .

I tried to place them in the react cycle functions (such as componentDidMount() ,...) since some trigger after render, but nothing did.

Basically, this is what I did:

Service.jsx

function getAll() {
    // I had saved some values in the localStorage
    const uid = JSON.parse(localStorage.getItem('user')).uid;

    // Here the reference to appropriate child in the database
    const dbRef = firebase.database().ref().child('events');

    // Choosing the user with appropriate's is
    const userRef = dbRef.child(uid);

    // An array to save fetched values
    let answer = [];

    // Created a listener
    userRef.on('value', snap => {
        let rx = snap.val();
        for (let item in snap.val()) {
            answer.push(rx[ item ]);
        }

        // Return a promise for the actions
        return Promise.resolve({
            events: answer
        });
    });

    return Promise.resolve({
        events: answer
    });

}

Reducer.jsx

export function events(state = { events: [] }, action) {
    //    --------> Get all
    case eventConstants.GET_ALL_REQUEST:
        return {
            ...state,
            managingEvent: true,
        };
    case eventConstants.GET_ALL_SUCCESS:
        return {
            ...state,
            events: action.events
        };
    case eventConstants.GET_ALL_FAILURE:
        return state;
    default:
        return state
    }
}

Action.jsx

function getAll() {
    return dispatch => {
        dispatch(request());

        eventService.getAll()
            .then(
                ans => {
                    dispatch(success(ans.events));
                },
                error => {
                    dispatch(failure(error));
                    dispatch(alertActions.error(error));
                }
            );
    };

    function request() {
        return { type: eventConstants.GET_ALL_REQUEST }
    }

    function success(events) {
        return { type: eventConstants.GET_ALL_SUCCESS, events }
    }

    function failure(error) {
        return { type: eventConstants.GET_ALL_FAILURE, error }
    }
}

Grob:

This is what I've done. Now there's what I'm trying to do: retrieve datas using store.dispatch(Action.getAll()) because I use those data in the search Component. I use them in this way:

Search

// usual imports here, connect inculed
import React, { Component } from 'react';
import { connect } from 'react-redux';

class SearchPage extends Component {

    // So I first used the constructor
    constructor(props){
        this.props.dispatch(Action.getAll());
    }

    componentDidMount() {
        // Then here... an same for all others methods of react lifecycle
        this.props.dispatch(Action.getAll());
    }

    render() {

    // TODO Don't forget the translation
    let { translate, events } = this.props;

    ....

    const table = (
        <Table hover>
            {tableHeader}
            <tbody>
            {events.map(item =>
                <tr key={item.key}>
                    <th scope="row">{item.key}</th>
                    <td>{item.name}</td>
                    <td>{item.startDate}</td>
                    <td>{item.endDate}</td>
                </tr>
            )}
            </tbody>
        </Table>
    );
...
}


const mapStateToProps = (state) => {
    const { events } = state.events;
    const translate = getTranslate(state.locale);
    return {
        events,
        translate,
    };
}

const connectedPage = connect(mapStateToProps)(SearchPage);
export { connectedPage as SearchPage };

Marks:

Sometimes it worked fined, then when I updated the database online and then it re-rendered, it said that event was null or undefined, which I could understand, because of the transition in Reducer.js .

But what should I do now ? is now my question.

Thank you for reading this text and thank you for your help.

:)

The problem here is that the getAll function inside the service will not wait for listener to complete. Because of this, the answer variable will always have [] .

Solution is to return a new promise instance which gets resolved only after receiving the data from firebase.

Updated Service.jsx

function getAll () {
  // I had saved some values in the localStorage
  const uid = JSON.parse(localStorage.getItem('user')).uid;

  // Here the reference to appropriate child in the database
  const dbRef = firebase.database().ref().child('events');

  // Choosing the user with appropriate's is
  const userRef = dbRef.child(uid);

  // An array to save fetched values
  const answer = [];
  return new Promise((resolve) => {
    // Created a listener
    userRef.on('value', (snap) => {
      const rx = snap.val();
      for (const item in snap.val()) {
        answer.push(rx[item]);
      }

      // Return a promise for the actions
      resolve({
        events: answer
      });
    });
  });
}

I believe the return Promise... at the end of getAll() (outside the userRef.on('value'... ) on Services.jsx causes the function getAll to return undefined if the fetch of events is not completed yet. I would refactor Service.jsx like this (Firebase methods return promises already, so you can return them directly):

function getAll() {
    const uid = JSON.parse(localStorage.getItem('user')).uid;
    const dbRef = firebase.database().ref().child('events');
    const userRef = dbRef.child(uid);

    let answer = [];
    return userRef.on('value', snap => {
      snapshot.forEach((snap) => answer.push(snap.val())); 
      return {
        events: answer
      };
    });
}

Another approach you may try is to create a special component that does not render anything, being only responsible for attaching a firebase listener on componentDidMount and detaching it on componentWillUnmount (something like the code below), and them import this special component inside your SearchPage.jsx.

class FirebaseListenToEvents extends Component {
  componentDidMount() {
    const uid = JSON.parse(localStorage.getItem('user')).uid;
    const dbRef = firebase.database().ref().child('events');    
    this.userRef = dbRef.child(uid);

    this.userRef.on('value', (snap) => {
      this.props.onUpdateEvent(snap.key, snap.val());
    });
  }

  componentWillUnmount() {
    this.userRef.off('value');
  }

  render() {
    return null;
  }
}

I'm not sure which approach will work best for you but I hope this gives you some insights on how to deal with Firebase & React. This article also seems to explain it better than me. If you have problems just ask me on the comments. Good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM