Cannot setState() with Firebase database listener - reactjs

I'm trying to build a mini-game with rooms. When I try to retrieve room information from my firebase realtime database, I used database.ref().on() function to listen on any data changes and setting my state according to the change. Here is what the code looks like:
export default class Room extends Component {
state = {
room: null,
username: "sdff",
};
componentWillMount() {
const roomId = this.props.location.pathname;
app()
.database()
.ref(`/rooms${roomId}`)
.on("value", (snapshot) => {
const data = snapshot.val();
console.log(data);
this.setState({ room: data });
this.setState({ room: "hello" });
});
}
When I do console.log(data), I actually do see that data contains all information that I want. However, this.setState is not working, which causes the page failing to render since lots of the information from state is used in render(). Even if I set room to "hello", as shown in the last statement of the code snippet, it still fails to set state. I'm wondering if it's because render() is called before the data is successfully retrieved? If so, how can I fix this? Thanks in advance.

I think you need componentDidMount() instead.
As React official docs say:
This method is a good place to set up any subscriptions
Also, don't forget to end the subscription on componentWillUnmount()

Related

How to retrieve user data from fire store

I need to fetch the user data and display it. I am getting an error now that says
TypeError: this.unsubscribe is not a function
and when I initialise it as a normal variable like const db, then I get another error
Function CollectionReference.doc() requires its first argument to be of type non-empty string
import React from "react";
import { auth, firestore } from "../../firebase/firebase.utils";
export default class UserPage extends React.Component {
state = {
user: {}
};
unsubscribe = null;
componentDidMount() {
const user = auth.currentUser;
this.unsubscribe = firestore
.collection("users")
.doc(user)
.onSnapshot(doc => {
this.setState = {
user: doc.data()
};
});
}
componentWillMount() {
this.unsubscribe();
}
render() {
return (
<div>
<h1>{this.state.user.name}</h1>
</div>
);
}
}
when I insinalise it as a normal variable like const db
Not quite sure what you mean by this, but if you're getting an error about the type of unsubscribe, I suggest using console.log right before you call it to view its value.
Bear in mind that componentWillMount happens in the lifecycle before componentDidMount (hence the names will and did). I suspect that's one of your problems: you try to call unsubscribe before setting the value.
With regard to your other error about the doc call, it's likely referring to:
...
.collection("users")
.doc(user) <-- this line
.onSnapshot(doc => {
this.setState = {
us
...
As the error output states, that user variable (the first argument of doc) must be a string, and it can't be an empty string.
I don't see user anywhere in your code, so I expect that it's currently the value undefined. You could access this.state.user here, but I'd strongly advise against it since you subsequently set that state in the call (probably cause an infinite loop).
What is your end goal? What have you tried to resolve these two issues? Maybe adding that to your question would help us assist you better.

how to fix the syntax error i getting in react component?

I work by a tutorial to create a fullstack react web app with backend connected to mongo , now while trying to merge it with my previous code I getting a syntax error..
i try to search in google but none of it help
this is my console error
module build failed(from ./node_modules/babel-loader/lib/index.js);
syntaxError: c:/users/aviram/zofim/client/src/app.js: unexpected token (49:16)
49| getDataFromDb = () => {
this is my code
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
// initialize our state
constructor(props){
super(props);
this.state = {
data: [],
id: 0,
message: null,
intervalIsSet: false,
idToDelete: null,
idToUpdate: null,
objectToUpdate: null
};
this.getDataFromDb = this.getDataFromDb.bind(this);
}
// when component mounts, first thing it does is fetch all existing data in our db
// then we incorporate a polling logic so that we can easily see if our db has
// changed and implement those changes into our UI
componentDidMount() {
this.getDataFromDb();
if (!this.state.intervalIsSet) {
let interval = setInterval(this.getDataFromDb, 1000);
this.setState({ intervalIsSet: interval });
}
}
// never let a process live forever
// always kill a process everytime we are done using it
componentWillUnmount() {
if (this.state.intervalIsSet) {
clearInterval(this.state.intervalIsSet);
this.setState({ intervalIsSet: null });
}
}
// just a note, here, in the front end, we use the id key of our data object
// in order to identify which we want to Update or delete.
// for our back end, we use the object id assigned by MongoDB to modify
// data base entries
// our first get method that uses our backend api to
// fetch data from our data base
getDataFromDb = () => {
fetch('http://localhost:3001/api/getData')
.then((data) => data.json())
.then((res) => this.setState({ data: res.data }));
};
I`d like it to compile
To solve your problem change this line getDataFromDb = () => { to getDataFromDb(){.
This is happening because you don't have class properties configured on your build configurations, a plugin for babel: https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
Note that this syntax getDataFromDb = () => { is not approved yet, the proposal and more info on class properties: https://github.com/tc39/proposal-class-fields
Actually the problem is here,
let interval = setInterval(this.getDataFromDb, 1000);
You should call your getDataFromDb function like this,
let interval = setInterval(this.getDataFromDb(), 1000);
Also as you are using arrow function, no need to bind this,
this.getDataFromDb = this.getDataFromDb.bind(this); //You can remove this line
Demo.
Note:: Here fetch call changed to some other URL to get it work.
If you are using arrow function then there is no need to bind. So remove this.getDataFromDb = this.getDataFromDb.bind(this); this line it will work fine.
You forgot to complete closing bracket } at the end. If you are using arrow function then there is no need of bind so remove this line 'this.getDataFromDb = this.getDataFromDb.bind(this);'

React-router history.listen not running in componentWillMount

I'm trying to make a SearchResults component which shows search results based on what's in the query string, which is updated by a SearchForm component. It has been recommended here that I use history.listen. Great idea, except for some reason when called in componentWillMount, history.listen is not being triggered:
componentWillMount() {
let location;
this.props.history.listen(routerState => {
console.log('current location:' + routerState); //never logs anything...
location = routerState;
});
console.log(location); //'undefined'
let search = location;
console.log(search); //'undefined'
this.setState({ search: search.search }); //throws "Cannot read property search of undefined error
}
This seems strange, since I use history.listen here pretty much exactly as the previous poster did in that link. I have also tried putting this logic in componentDidMount with the same results.
My component is wrapped by withRouter here.
Solved this by implementing logic inside the listen call, as well as putting initialization logic inside of componentDidMount--this is necessary because history.listen is only triggered on change, not on the initial render.
My code:
componentWillMount() {
this.unlisten = this.props.history.listen(location => {
console.log('current location:' + location);
let query = qs.parse(location.search);
console.log(query) //logs an object with attributes based on the current query string
});
}
componentWillUnmount() {
this.unlisten();
}
componentDidMount() {
let query = qs.parse(this.props.location.search);
console.log(query); //logs an object with attributes based on the current query string
}

Realm js run query async

I'm using React-Native with Realm Database.
I tried to run read queries using the async/await pattern but it looks like it's always executing synchronously, freezing the UI since the query takes at least a couple of second till the rendering (due to a possible large amount of data stored).
This is the code I'm using (it's a copy and paste, but to give you an idea), am I missing something?
export default class CustomComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value:0
};
this.realm = this.buildRealm();
}
buildRealm() {
return new Realm({ schema: [EventSchema] });
}
componentWillMount() {
this.fetchData().then(result => { this.setState(value:result);});
}
async fetchData() {
var appState = await someMethod()
return appState;
}
someMethod() {
return new Promise(resolve => {
resolve(queryFromDB())
});
}
queryFromDB() {
// Returns a value fetched from Realm
let events = this.realm.objects("Event");
return events.length;
}
render() {
return (
<Text> {this.state.value} </Text>
);
}
}
Realm query is synchronous, there is no way to make it async.
Look like you are using realm with React Native, if this is the case, the best solution is to defer your realm query using the interaction manager
async componentDidMount() {
// loading objects from realm is a synchronous task that block the main
// process, in order not to freeze the ui, we trigger the load after
// the interactions and screen loading is done
this.getRealmDataHandle = InteractionManager.runAfterInteractions(() => this.getRealmData())
}
componentWillUnmount() {
if (this.getRealmDataHandle !== undefined) {
this.getRealmDataHandle.cancel()
}
}
First your component mount and render a loading screen, then when the ui rendering thread has finished it's work, the interaction manager will trigger the realm query. This way, the user is not experiencing too much of UI freezing.
I hope someday the realmJS team will provide an async version, but I doubt this will happen soon.
Have you tried fetching data on componentDidMount instead of willMount?
WillMount requires your functions to complete before mounting begins.
You can then add a loading UI state (spinner, sample text item etc).
Then perhaps make your state.value a default which then you update when your query is complete.

What is a best way to get value from AsyncStorage before whole app load

I have App.js file and it is root of my application (both ios and android reference to it).
I have a value that I keep in AsyncStorage that I need before app.js render method is called.
Problem is that as it is async it late and I can't get that value.
class App extends React.Component {
constructor(props) {
super(props);
this.init()
}
async init() {
try {
const value = await AsyncStorage.getItem('#myPoorValue:key');
if (value !== null){
...
}
} catch (error) {}
}
}
...
render (...
I hope that I explained good what is my issue here.
I know that there is no way to get it synchronous (I would like that) but don't know what to do in this situation.
To explain it a bit better I use I18n and I manually set I18n.locale to some value and other components get default value before I set it manually.
Just to note I also use redux and I pass selected value to it.
try the following:
...
constructor(props) {
super(props)
this state = {
isLoading: true
}
}
async componentDidMount() {
await this.init()
// you might want to do the I18N setup here
this.setState({
isLoading: false
})
}
async init() {
const value = await AsyncStorage.getItem('#myPoorValue:key')
...
}
...
the thing is that init() returns a promise and you need to wait until it gets resolved. That's when await comes to rescue.
you'll also need to set up some loader that will be there on first render, and toggle the state to replace it with actual markup after the AsyncStorage value has been fetched. I've put it in the code, but you might want to trigger a redux action instead, depending on your setup.

Resources