Callback not called in database .on method - javascript

I'm using react-native-firebase package, react-native and redux.
When I'm trying to initialize data by fetching tasks from firebase database. I'm calling async redux action from my react component's componentDidMount() method.
componentDidMount() {
let uid = null;
if (this.props.sessionState.authUser) {
uid = this.props.sessionState.authUser.uid;
}
this.props.fetchToDos(uid);
}
And everything works fine on the first build of React Native app, but when I save and reload, for some reason the callback of my db call is not called and it keeps not being called until I completely rebuild my app. Again it worked fine on the first load of an app.
Here is the call to database:
export const fetchToDos = (uid) => async dispatch => {
var userId = firebase.auth().currentUser.uid;
database.ref().child('users/' + userId + '/tasks/').on('value', snapshot => {
console.log('INSIDE');
dispatch({
type: 'FETCH_TASKS',
payload: snapshot.val()
});
})
};
I'm porting my app from the web and this code works perfectly fine with my web app.

This Github issue seems like your problem, I think you should give it a look. Good luck!

Related

How to use manual cache updates using RTK Query

I have recently started working on redux toolkit, Basically i want to refetch the data from database if there is mutation. I am using flatlist in my react native project where on pull to refresh i want my "getPosts" endpoint to refetch and get updated.
Note: I know about using tags for automated cache invalidation. but on web RTK query is not implemented.
Talking about pessimistic update:
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
try {
const { data: updatedPost } = await queryF`enter code here`ulfilled
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, updatedPost)
})
)
} catch {}
},
just give a help how to call this function from APP.JS or anywhere from the app. Thanks
You can just call refetch, you don't need anything that complicated. See the docs on useQuery
const myResult = useMyquery(args)
// in your pull to refresh trigger:
myResult.refetch()
If you are outside of a component, you can also still
store.dispatch(api.endpoints.myEndpoint.initiate(args, { track: false })))
to trigger a refetch

Asynchronous Firebase data get on React Native

I'm building a React Native app using Firebase. I use the following method (among others) in one of my components to get data from Firebase:
loadMessagesFromFirebase(chatId){
let messageList = [];
let data = {};
let message = {};
const dataRef = firebase.database().ref('chats').child(chatId);
dataRef.on('value', datasnap=>{
data = datasnap.val()
})
for (var sayer in data){
for (var m in data[sayer]){
message = {sayer: sayer, text: m.text, time: new Date(m.created)};
messageList.push(message);
}
}
messageList.sort((a,b) => (a.time > b.time) ? 1: -1);
this.setState({messageList: messageList});
}
The problem is that occasionally, data will load as an empty dictionary (Object {}) and therefore, the message list will be empty. I assume this happens because I'm not giving Firebase enough time to load. How do I make this function asynchronous, so that I can add a "loading" state to the component and not display message information until it's finished loading?
async componentDidMount(){
firebase.initializeApp(FirebaseConfig);
this.loadMessagesFromFirebase(this.state.chatId);
//once the messages are done loading
this.setState({{loading: false}})
}
Additionally, is there a way to make sure that, if data is returned as an empty dictionary, it's because Firebase finished loading and NOT because there isn't data for this chat id?
Answering this question even though OP seems to figured out the answer, since he hasn't explained the underlying concepts.
Firebase sdk uses async programming and observer pattern to provide real time updates.
The right way to Asynchronous Firebase data get on React Native world be as follows.
Initialize firebase sdk only once during application startup. In React terms this can be done inside the constructor of the top level App component.
See firebase docs for the steps. https://firebase.google.com/docs/database/web/start
Inside component constructor or componentDidMount set up the call to firebase function to load data
componentDidMount(){ this.loadMessagesFromFirebase(this.state.chatId); }
In the load messages function at up async call to the firebase realtor database. More reading here https://firebase.google.com/docs/database/web/read-and-write
The main thing to remember here is that all code that has to run after data is available has to be triggered from writing the async call back. Modifying the example code from the question
loadMessagesFromFirebase(chatId){
let data = {};
let output = {};
const dataRef = firebase.database().ref('chats').child(chatId);
dataRef.once('value', datasnap=>{
data = datasnap.val();
// Do something really smart with the data and assign it to output
......
output = data;
// set updates to the state
this.setState({output: output});
})
}
Note the use of once instead of the on function.
The reason for this is that for getting subscription to the observer on can lead to the callback being triggered every time there is a change in data. This can lead to undesirable consequences if the component was designed to only get data once.
Further reading https://firebase.google.com/docs/database/web/read-and-write
In case it is desirable to have the component updated every time there is a data change then use the on function to set up a subscription to that data. However in such a case it is important to cancel the subscription inside of componentWillUnmount
https://firebase.google.com/docs/reference/js/firebase.database.Reference#off
This can be summarized as follows
`
// inside componentDidMount
this.onValueChange = ref.on('value', function(dataSnapshot) { ... });
// Sometime later.... Inside componentWillUnmount
ref.off('value', this.onValueChange);`
Figured it out thanks to vvs' comment. Everything that has to be done asynchronously has to go into that dataref.on() function.
loadMessagesFromFirebase(chatId){
let messageList = [];
let data = {};
let message = {};
const dataRef = firebase.database().ref('chats').child(chatId);
dataRef.on('value', datasnap=>{
data = datasnap.val()
for (var sayer in data){
for (var m in data[sayer]){
message = {sayer: sayer, text: m.text, time: new Date(m.created)};
messageList.push(message);
}
}
messageList.sort((a,b) => (a.time > b.time) ? 1: -1);
this.setState({messageList: messageList});
this.setState({{loading: false}})
})
}
And this is how the function is called:
componentDidMount(){
this.loadMessagesFromFirebase(this.state.chatId);
}
(Having firebase.initializeapp(FirebaseConfig) in there actually causes problems- it has to run once, and earlier, such as when setting up the app)

How to use onSnapshot to update props through Redux

I'm learning how to code and I'm really struggling with the following issue:
I'm building a chat app and I need to display new messages as soon as they are submitted. As a result, I thought about using onSnapshot(). Every time I used onSnapshot() in basic one-page exercises, it worked. Now, however, I have a project with 3 folders (src; public; functions). For all my functionality, I got/sent data using this.props.functionName() and manage state with Redux and then, the backend would return a promise.
My question is, how do I use onSnapshot to update my props given that I cannot call the function nor use return.
I tried using the admin sdk directly in the components, but firebase issued a warning in the console saying I should only use it in the backend.
Basically, how do I send the following array to the component I need it to be sent to without invoking the function?
exports.messages = (req, res) => {
db.collection('messages').onSnapshot(snapshot => {
let messages = [];
snapshot.forEach(doc => {
messages.push(doc.data())
});
})
}
Thanks in advance!
You can find in this tutorial a way to update firebase props that might work for you. The sample code they use is:
useEffect(() => {
const unsubscribe = props.firebase
.db.collection('myCollectionName')
.onSnapshot(snapshot => {
if (snapshot.size) {
// we have something
** Handle returned data **
} else {
// it's empty
}
})
return () => {
unsubscribe()
}
}, [props.firebase])
And this other one for handling returned data:
let myDataArray = []
snapshot.forEach(doc =>
myDataArray.push({ ...doc.data() })
)
setData(myDataArray)

Is it recommended to have a mobx Store for each page?

I am building a single page application with Reactjs and MobX at the frontend (port 3000) and Nodejs and Express at the backend (API, port 4000). I am new to both, MobX and Reactjs and I am trying to set up a well-structured project.
My question is: Is it okay to have a Store for each view?
For example, I have a UserStore which stores the Session information and takes care of the login and logout of the user within the platform. However, after Logging in, I want to redirect the user to the dashboard page. This dashboard page must retrieve information regarding the user, but also it must contact the API and retrieve some data (i.e. Some Todos).
This is how I would do it:
This is the login function in which the redirection to Dashboard is made:
*UserStore.js*
[...]
import navigationStore from './NavigationStore';
[...]
login = async (user) => {
try {
const res = await axios.post('/session/login', {
username: user.username,
password: user.password
});
this.saveUser(res.data);
navigationStore.push('/dashboard');
} catch (error) {
[...]
}
}
And, then, I have created a DashboardStore.js which has the following code:
*DashboardStore.js*
[... imports and initializations ...]
class Store {
#observable todos = null
constructor() {
this.getDashboard();
}
#action('Load dashboard') getDashboard = async () => {
const res = await axios.get('/api/dashboard/', {});
this.todos = res.todos
}
}
const DashboardStore = new Store();
export default DashboardStore;
But this would mean that I'd end up doing another Store for the Todos page and another Store for whatever page I'd need.
In NodeJs you can make a controller for each class and there's nothing weird about it. However, I'm not sure that's how it works on MobX.
It depends on the complexity of your app. I wouldn't create a store for each view or concern, but you could create two, like the MobX docs recommend: https://mobx.js.org/best/store.html.
I'm working on a bigger project right now, and we started with a single store for everything. Obviously, it grew a lot as we kept adding functionality, so I think we might split it at some point to reduce complexity.

Nuxt.js Loading data serverside with nuxtServerInit and Vuex

Currently i am working on storing data for a job opening application.
For the backend i use Laravel and for the frontend i use Nuxt.js
I am new to Nuxt, so i'm kinda stuck on the following issue.
I have a page for creating a new job opening called new-job.vue. I also created a store called jobs.js for handling the states.
On new-job.vue i have a form with data that has to be rendered in a list before the form starts.Like a list of all countries etc.. in order for me to select them in the form.
At this point i'm using asyncData within the export default on new-job.vue:
<script>
export default {
asyncData(context) {
return context.app.$axios
.$get('jobs/create')
.then(data => {
//context.store.dispatch('jobs/getTypes', data.types)
context.store.dispatch('jobs/getPlatforms', data.platforms)
context.store.dispatch('jobs/getCountries', data.countries)data.branches)
// return {
// loadedPost: { ...data, id: context.params.postId }
// }composer required barr
})
.catch(e => context.error(e))
},
computed: {
types () { return this.$store.state.jobs.types },
platforms () { return this.$store.state.jobs.platforms },
countries () { return this.$store.state.jobs.countries },
},
}
The asyncData method works and the lists of types, platforms and countries are getting filled with data from the database and the state from the Vuex store gets updated. .Only the data is being rendered on the client side.
I prefer this data to be loaded server side, so i was looking at nuxtServerInit. Only can someone explain to me how i can make this happen.
I placed an async call inside the export default of new-job.vue:
async nuxtServerInit ({ commit, state }, { app }) {
let res = await axios.get(`jobs/create`)
console.log(res)
commit('setPlatforms', res.data.platforms)
commit('setTypes', res.data.types)
commit('setCountries', res.data.countries)
},
I created the commits in the mutations of the jobs.store, but the states are not being updated.
What am i doing wrong and/or what am i missing?
Or maybe another question, is nuxtServerInit the way to go? Or is loading these lists of data on the clientside not a big deal?
UPDATE:
I use modules mode for the store, so i created a store called jobs.js. Inside this file i tried to call nuxtServerInit as well, but i didn't get any response.
nuxtServerInit(vuexContext, context) {
return context.app.$axios
.$get('jobs/create')
.then(data => {
console.log(data)
})
.catch(e => context.error(e))
},
From nuxtServerInit API reference in Nuxt.js documentation:
If the action nuxtServerInit is defined in the store, Nuxt.js will call it with the context (only from the server-side).
In other words, it is a reserved store action available only in store/index.js file and if defined will be called on server-side before rendering requested routes.
Only asyncData and fetch methods are available within pages.

Categories