Reading data from Firebase 3 into a React component with ES6 syntax - reactjs

I've been struggling with a simple use case: read a number, ie signed-up user counts, on my React web app from Firebase Database and display it when the page is loaded.
There is documentation for older versions of React and Firebase, but it would seem that this use case should be doable the new way.
Screenshot of my Firebase Database.
import React from 'react'
import {Badge} from 'react-bootstrap'
var firebase = require('firebase')
var config = {
apiKey: "MyKey", // redacted key
authDomain: "dnavid-c48b6.firebaseapp.com",
databaseURL: "https://dnavid-c48b6.firebaseio.com",
storageBucket: "dnavid-c48b6.appspot.com",
};
var firebaseApp = firebase.initializeApp(config);
var UCRef = firebaseApp.database().ref("numberofusers")
class UserCount extends React.Component{
constructor(props){
super(props)
this.state = {usercount: '3'}
}
componentDidMount() {
// this.setState({usercount:4}) // This actually works and displays "4"
var uc = UCRef.on('value',function(snapshot){return(snapshot.val())})
debugger;
this.setState({usercount:uc}) // This does not work
}
render(){
return (
<Badge>
{this.state.usercount}
</Badge>
)
}
}
export default UserCount;
In the Chrome debugger (note "debugger" command) I get in the console:
> uc
<. function(snapshot) {
return snapshot.val();
}
And
> uc()
Uncaught TypeError: Cannot read property 'val' of undefined(…)
From which I would deduce that since the snapshot is undefined, the event has not been triggered. But this doesn't sound logical as:
componentDidMount has been evaluated (because the component has rendered and the execution reaches it when the debugger stops for inspection)
Firebase documentation claims that the event is triggered, see below "This method is triggered once when the listener is attached".
Value events
You can use the value event to read a static snapshot of the contents
at a given path, as they existed at the time of the event. This method
is triggered once when the listener is attached and again every time
the data, including children, changes. The event callback is passed a
snapshot containing all data at that location, including child data.
If there is no data, the snapshot returned is null.
So, is the listener not being attached? What's going on?

You need to do it like this:
componentDidMount() {
UCRef.on('value', snapshot => {
this.setState({usercount: snapshot.val()});
});
}
UCRef.on('value', func) would not return you a value. You can get it inside a callback

Related

Building Credential object needed for Firebase Reauthentication

When trying to build the credential object to reauthenticate a user with Firebase and ReactJS, I am getting cannot read property 'credential' of undefined where undefined is referring to this.app.auth.EmailAuthProvider which should be firebase.auth.EmailAuthProvider.
I have read that it is a static method and cannot be called on an instance, but I am not sure what exactly that means or how to correct my implementation to get the credential needed. I am calling the method in a class based component, but I am still unaware of how all of this ties in to calling a static method.
The method that I am calling is 'this.app.auth.EmailAuthProvider.credential()'
reAuthUser = (data) => {
// console.log('email: ', data.userEmail, 'password: ', data.password);
const userCredential = this.app.auth.EmailAuthProvider.credential(
data.email,
data.password
)
// this.currentUser.reauthenticateWithCredential(data.userEmail, data.password)
// .then(function() {
// alert('user has been reauthenticated')
// }).catch(function(error) {
// console.log('reauth error: ', error)
// })
};
This is in ReactJS, in a class component. this.app is a reference to Firebase and it is called in the constructor:
constructor(props) {
super(props);
this.app = Firebase;
this.currentUser = this.app.auth().currentUser;
};
I know similar questions have been asked and answers have been approved, but they don't make much sense to me at this point.
Assuming that this.app.auth is an instance of the firebase.auth.Auth class, EmailAuthProvider won't be present on that object, as it is not part of the prototype for the firebase.auth.Auth class.
EmailAuthProvider is instead part of the firebase.auth namespace which means it can only be accessed using firebase.auth.EmailAuthProvider (note how auth is not called as a function).
If using imports, you could also use
import { auth as FirebaseAuth } from 'firebase';
FirebaseAuth.EmailAuthProvider.credential(...)

setState() causes state variable to be undefined

I am, for the most part, following this tutorial.
My Django API's set up well. I have this service function:
export default class GoalService{
getGoals() {
const url = `${API_URL}/api/goals`;
return axios.get(url).then(response => response.data);
}
}
Which is called by the componentDidMount method in my GoalList:
class GoalTable extends Component {
constructor(props) {
super(props);
this.state = {
goals: [],
now: now.getDate(),
}
}
componentDidMount() {
var self = this;
goalService.getGoals().then(function (result) {
console.log(result);
self.setState({ goals: result.data })
});
}
render() { ... }
(This is step 8 of the above-linked tutorial).
Now, when I try to use { this.state.goals.map(...) }, I get the error TypeError: this.state.goals is undefined. Looking at other threads, a lot of people seem to have had this problem—but it comes about because they've used setState() outside of the request being made and, since setState() is asynchronous, the state is set to something blank. I'm using it inside of a call to then, so I don't think that's the issue.
I tried adding a second argument to then (in case this operation wasn't successful), but, the getGoals() call is successful, and successfully prints out the JSON sent back by Django's API. Similarly, I can see that the request went as expected in the Network tab of the developer tools.
What could be going wrong here? Why isn't the state properly updating w/ the returned JSON?
As mentioned in the comments, the tutorial has a typo, which means that the code tries to access response.data.data instead of response.data.
The fix would be to remove this extra level of drilling down into the object:
componentDidMount() {
var self = this;
goalService.getGoals().then(function (result) {
self.setState({ goals: result }) // no .data
});
}
Also, note that you could make this code simpler by using arrow functions (which automatically bind the this from the place that they're defined) and the object initialization shorthand:
componentDidMount() {
// { goals } is the same as { goals: goals }
goalService.getGoals().then(goals => this.setState({ goals }));
}

Meteor React tutorial interacting with mongo not working

I have been trying for a while to learn how to build mobile apps with Javascript, and honestly I have no idea how anyone is able to do anything. Everything is broken. Every tutorial I've tried has failed to work for some bizarre reason. I am at my wits end.
I've finally decided to try and be even simpler, and just do the most basic tutorial I can find. What could go wrong. Well, it only took 3 pages of almost no code to completely stop working. I've done this, and I cannot insert anything to my db. My app fetches no data. When trying to add a new task, it gets added then disappears almost immediately, with a message stating insert failed: Method '/tasks/insert' not found (not even an error with some traceback).
The code really couldn't be simpler:
// imports/api/tasks.js
import { Mongo } from 'meteor/mongo';
export const Tasks = new Mongo.Collection('tasks');
// imports/ui/App.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { withTracker } from 'meteor/react-meteor-data'
import { Tasks } from '../api/tasks.js';
import Task from './Task.js';
// App component - represents the whole app
class App extends Component {
handleSubmit(event) {
event.preventDefault();
// find the text field via the react ref
const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
Tasks.insert({ text, createdAt: new Date() });
// Clear form
ReactDOM.findDOMNode(this.refs.textInput).value = '';
}
renderTasks() {
return this.props.tasks.map((task) => (
<Task key={task._id} task={task} />
));
}
render() {
return (
<div className="container">
<header>
<h1>Todo List</h1>
<form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
<input
type="text"
ref="textInput"
placeholder="Type to add new tasks"
/>
</form>
</header>
<ul>
{this.renderTasks()}
</ul>
</div>
);
}
};
export default withTracker(() => {
return {
tasks: Tasks.find({}).fetch(),
};
})(App);
What is wrong? What am I missing?
The tutorial is indeed out of date and should be updated.
Background
In June 2017 there was a big security issue with allow/deny identified and the feature has been blocked since then.
Meteor allowed you to define client collection, that automatically synced with the server when the methods insert, update, remove were called on the client.
In order to control the access permissions, the allow/deny feature was implemented.
Now without allow/deny you will get the insert failed: Method '/tasks/insert' not found when classing SomeCollectionOnClient.insert but since this feature is obsolete (you will even get a big warning when setting it up), you need to create a server side method and call it from the client in order resolve this issue:
On the server create this method and ensure it is in the import chain from server/main.js:
new ValidatedMethod({
name: 'tasks.insert',
validate(args) {
// better use simpl-schema here
if (!args.text || !args.createdAt) {
throw new Meteor.Error('incompleteArgs', 'args are incomplete')
}
},
run (args) {
// check user permissions...
return Tasks.insert({ text, createdAt })
}
})
In your client component you can then call it via:
// find the text field via the react ref
const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
Meteor.call('tasks.insert', {text, createdAt: new Date()}, (err, res) => {
// do something on err / on res
})
Note that this couples your component to the server side method and you may rather try to implement some containers for your pages that handle all the connection / pub-sub / method calling activity wile your components solely render content.
More to read / used in this answer:
https://guide.meteor.com/react.html
https://guide.meteor.com/security.html
https://docs.meteor.com/api/methods.html#Meteor-call
https://guide.meteor.com/methods.html#validated-method

Property 'calendar' does not exist on type 'typeof client'

I'm trying to connect my Google Calender to my React website. I've got a component called Calendar. I've used the JS tutorial from Google and I've changed it to work in Typescript. I've got the authentication and authorization already working, however fetching data from the calendar is not working. I'm getting the following error when compiling/editing.
[ts] Property 'calendar' does not exist on type 'typeof client'. Did you mean 'calendars'?
I've already downloaded the types for the gapi.client.calendar and as you can see in the image below, they are also found in the #types folder. I'm kind of stuck and I don't know how I can fix this issue..
Here is my code from my Calendar.tsx
import * as React from 'react';
import { Button } from 'semantic-ui-react'
import googleApiKey from '../googleapi-key.json';
const CLIENT_ID = googleApiKey.CLIENT_ID;
const API_KEY = googleApiKey.API_KEY;
const DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"];
const SCOPES = "https://www.googleapis.com/auth/calendar.readonly";
class Calendar extends React.Component {
constructor(props: any) {
super(props);
console.log(CLIENT_ID);
console.log(API_KEY);
this.handleClientLoad = this.handleClientLoad.bind(this);
this.handleAuthClick = this.handleAuthClick.bind(this);
this.handleSignoutClick = this.handleSignoutClick.bind(this);
this.initClient = this.initClient.bind(this);
this.updateSigninStatus = this.updateSigninStatus.bind(this);
this.listUpcomingEvents = this.listUpcomingEvents.bind(this);
}
componentDidMount() {
this.initClient();
}
public render() {
return (
<div>
<Button onClick={this.handleAuthClick}>
authorizeButton
</Button>
<Button onClick={this.handleSignoutClick}>
signoutButton
</Button>
</div>
);
}
/**
* On load, called to load the auth2 library and API client library.
*/
public handleClientLoad() {
gapi.load('client:auth2', this.initClient);
}
/**
* Sign in the user upon button click.
*/
public handleAuthClick(event: any) {
gapi.auth2.getAuthInstance().signIn();
}
/**
* Sign out the user upon button click.
*/
public handleSignoutClick(event: any) {
gapi.auth2.getAuthInstance().signOut();
}
/**
* Initializes the API client library and sets up sign-in state
* listeners.
*/
public async initClient() {
await gapi.client.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES
})
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(this.updateSigninStatus);
// Handle the initial sign-in state.
this.updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
}
/**
* Called when the signed in status changes, to update the UI
* appropriately. After a sign-in, the API is called.
*/
public updateSigninStatus(isSignedIn: any) {
if (isSignedIn) {
this.listUpcomingEvents();
}
}
/**
* Print the summary and start datetime/date of the next ten events in
* the authorized user's calendar. If no events are found an
* appropriate message is printed.
*/
public listUpcomingEvents() {
console.log(gapi.client.calendar); // <--- Compile error: Does not recognize calendar
}
}
export default Calendar;
EDIT
When performing console.log(gapi.client) I can see that the client contains a calendar object (see image). But why can't I reach it in my own code?
I managed to fix my own problem. After performing console.log(gapi.client) I noticed that calender was already there, so I tried the following gapi.client['calendar'] and it worked as it should. I don't know why Typescript does not recognize the calendar in the first place, so if anybody has an idea feel free to leave a comment.
Try the following
Install types npm i #types/gapi.client.calendar
Include https://apis.google.com/js/api.js & https://apis.google.com/js/platform.js in index.html
Add the following inside types in tsconfig.app.json
"types": [
"gapi",
"gapi.auth2",
"gapi.client",
"gapi.client.calendar"
]
You have to add:
"types": ["gapi", "gapi.auth2", "gapi.client", "gapi.client.calendar"]
in tsconfig.app.js and in tsconfig.json.

How to delay component rendering when response from the server is delayed

I am a newbie to react. I have a situation where in I am fetching some data from the server which I want to display in a page. I am using websockets for communication between the client and server. As the server makes a request to some third party API the response gets delayed. But my component gets rendered before the response comes. I have seen answers which talks about handling such situation in case of ajax request. But how do i handle it in the case of web sockets. My sample jsx page which I want to render after getting response from server is as follows
import React ,{PureComponent} from 'react';
export default class ScorePanel extends PureComponent{
constructor(props){
super(props);
var d = new Date();
this.currentDate = d.getFullYear()+"-"+d.getMonth()+ "-"+ d.getDate();
this.week ="1";
this.numQuarters =1;
}
getInitialState(){
return {
resultsObtianed: false
}
}
getScores(){
return this.props.scores ||[];
}
render() {
return <div className = 'scorePanel'>
if ( !this.state.response ) {
return <div>loging response</div>
}
{
// data to render after geting response from server
}
}
How do I let the client know that response from the server has been received and it's time to render a component. It would be better if I can show a loading page if the response gets delayed. So I would like to make use of getInitialState function as well. I am dispatching an action to the server on a button click in the navigation bar. Thanks for the help in advance
Assuming you have a websocket ws, and listening to "test" event.
in your oncomponentDidMount do
componentDidMount()
{
ws.on("test",(data)=>{
this.setState({response:data})
})
}
also I like to predefine state, so I'd add this.state={response:{}} in constructor

Resources