Reputation: 748
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 }
}
}
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 };
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.
:)
Upvotes: 1
Views: 286
Reputation: 3487
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!
Upvotes: 1
Reputation: 480
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
});
});
});
}
Upvotes: 2