So, I'm working on a React project which uses Firebase to achieve lots of functionalities.
And now I'm trying to use some HTTPS callable functions in it.
But it seems like the way I import the 'firebase/functions' module is not correct. And it's giving me this error:
TypeError: Cannot read property 'httpsCallable' of undefined
Below is how I do the import and set up:
import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
import 'firebase/functions';
const config = {
// the config info here
};
class Firebase {
constructor() {
app.initializeApp(config);
this.auth = app.auth();
this.db = app.firestore();
this.functions = app.functions();
}
// trying to call the function
doCreatePlanner = this.functions.httpsCallable('createPlanner')
Can anyone point me to the right direction?
You are trying to access this.functions before you defined it in the constructor. To get rid of the error message, you could move the call to httpsCallable into the constructor:
constructor() {
app.initializeApp(config);
this.auth = app.auth();
this.db = app.firestore();
this.functions = app.functions();
const doCreatePlanner = this.functions.httpsCallable('createPlanner')
}
This is probably not exactly what you want to do, but in any event, you can't use this.functions until after you define it.
Related
I have Vue.js app and call API function from Vue component. The code looks as following:
component.vue:
<script>
import gpbApi from '#/utils/api/gpbApi';
methods: {
async load() {
const result = await gpbApi.catalogProducts.getList(this.entityId);
this.typesOfApplication = result;
},
gpbApi.js:
import catalogProducts from './catalogProducts';
export default {
catalogProducts,
};
catalogProducts.js:
import axios from 'axios';
axios.defaults.baseURL = 'http://localhost/client/';
export async function getList(entityId) {
if (entityId == null) return;
const { data } = await axios.get(`api/getList?id=${entityId}`);
return data;
}
gpbApi.js and catalogProducts.js files are placed in /utils/api/ folder.
I've got the following error:
mounted hook (Promise/async) TypeError: Cannot read properties of undefined (reading 'getList')
at VueComponent.load (component.vue:103:1)
How to overcome this issue?
You are using a default import although you didn't declare the default export.
Either use:
import * as gpbApi from '#/utils/api/gpbApi';
or
import { getList } from '#/utils/api/gpbApi';
I created a file which would contain helper functions for querying the database. My problem is that I have to get my access token through the Context API but I can't call the useContext hook outside of a functional component. I could place the functions inside one, but I don't need the component, it would be unused. What is the best practice here?
import React, { useContext } from "react";
import axios from "axios";
import { AuthContext } from "../../contexts/auth-context.js";
import { mongoQuery } from "../xhr/QueryMongo";
const { getAccessToken } = useContext(AuthContext);
export async function fetchUserData(userId) {
const sRealmApp = "...";
const CancelTokenLogin = axios.CancelToken;
const sourceLogin = CancelTokenLogin.source();
let token = await getAccessToken("users");
const userQuery = `query {user (query:{_id:"${userId}"}) {name, role, residence }}`;
let queryResult = await mongoQuery(token, sRealmApp, userQuery, sourceLogin);
if (queryResult.data.data !== null && queryResult.data.data.user !== null) {
return queryResult.data.data.user;
} else return false;
//other similar helper functions....
}
(Why I'm creating a new file: I'm refactoring my code because I have a file which has 400 lines of code, but it's not a big project. So I decided to extract code which connects to the database because it's not directly linked to the component.)
You can always provide the context as a parameter for your helper function.
helperFunction.js
fetchUserData(userId, token) {
... rest of code
}
Component.js
Then get the token from your component using useContext
import React, {useContext, useEffect} from 'react';
import context from 'location of context';
import fetchUserData from 'location of helper function';
const Component = () => {
const userId = 123; // Not sure where you are getting this, but for example.
const {getToken} = useContext(context);
useEffect(() => {
fetchUserData(userId, getToken());
}, []);
return (JSX)
};
Although, I don't think this is the best approach. I've done this before and it makes testing a nightmare. Creating a customHook would be a better approach imo.
We are trying to convert this json object timestamp:
Object {
"_nanoseconds": 725000000,
"_seconds": 1621386976,
}
to a firebase timestamp:
t {
"nanoseconds": 725000000,
"seconds": 1621386976,
}
Our code where the error is being thrown:
const lastItemIndex = thoughts.length - 1;
console.log(thoughts[lastItemIndex].date_created); // <--- logs Object timestamp
const seconds = thoughts[lastItemIndex].date_created._seconds;
console.log(seconds); // <--- logs seconds
const nanoseconds = thoughts[lastItemIndex].date_created._nanoseconds;
console.log(nanoseconds); // <---- logs nanoseconds
const lastDate = Firebase.firestore().Timestamp(seconds, nanoseconds); // <--- error
console.log(lastDate);
We are importing Firebase in our file like so:
import Firebase from '../../firebase';
And within the firebase.js file:
import * as firebase from 'firebase/app';
// Optionally import the services that you want to use
import 'firebase/firestore';
The warning we get:
[Unhandled promise rejection: TypeError: _firebase.default.firestore().Timestamp is not a function. (In '_firebase.default.firestore().Timestamp(seconds, nanoseconds)', '_firebase.default.firestore().Timestamp' is undefined)]
We have also tried the following:
const lastDate = new Firebase.firestore.Timestamp(seconds, nanoseconds);
and get the following error:
[Unhandled promise rejection: TypeError: undefined is not a constructor (evaluating 'new _firebase.default.firestore.Timestamp(seconds, nanoseconds)')]
We are following the docs to no avail. How can we convert this correctly?
Edit
exporting both Time_stamp and Firebase breaks the app [ the rest of the app does not recognize the Firebase export ]
export default Firebase makes everything back to normal. But the issue of converting the timestamp still remains
// Initialize Firebase
export const Firebase = firebase.initializeApp(firebaseConfig);
export const Time_stamp = firebase.firestore.Timestamp();
// export default Firebase;
The problem lies in how you are importing & exporting the library.
Reviewing your code
If this is where you are importing from the main library, you also need to make sure you are exporting it correctly. Looking at your current firebase.js file:
import * as firebase from 'firebase/app';
// Optionally import the services that you want to use
import 'firebase/firestore';
/* ... */
// Initialize Firebase
const Firebase = firebase.initializeApp(firebaseConfig);
export default Firebase; // <- this is a firebase.app.App not firebase itself
You are exporting an instance of firebase.app.App instead of firebase (the whole firebase library & namespace).
When you have an firebase.app.App instance, you can access Firestore of that app using app.firestore(). Because you import this app instance as Firebase in your main code, you confuse this with the normal firebase.firestore() which does something else.
To help illustrate the difference, take a look at this:
import * as firebase from "firebase/app";
import "firebase/firestore";
const config = { /* ... */ };
const defaultFirebaseApp = firebase.initializeApp(config);
// the instance of Firestore for the default app
const dbApp = defaultFirebaseApp.firestore();
// the instance of Firestore for the default app
const dbDefault = firebase.firestore();
// OR
// const dbDefault = firebase.firestore(dbApp);
console.log(dbApp === dbDefault); // -> true
const namedFirebaseApp = firebase.initializeApp(config, "something");
const dbNamedApp = namedFirebaseApp.firestore(); // returns instance of Firestore for the named app
// OR
// const dbNamedApp = firebase.firestore(dbNamedApp);
console.log(dbDefault === dbNamedApp); // -> false
Recommended export style
To properly export the Firebase library from firebase.js, you need to (and should) be using:
import firebase from 'firebase/app';
import 'firebase/firestore';
/* ... */
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
export default firebase; // re-export firebase library & namespace
By re-exporting the library this way, you can use it in the same way as all the code samples you encounter:
import firebase from '../../firebase';
const {_nanoseconds, _seconds} = thoughts[lastItemIndex].date_created;
const dateCreatedAsTimestamp = new firebase.firestore.Timestamp(_nanoseconds, _seconds);
const db = firebase.firestore();
db.doc("collection/doc")
.set({
date_created: dateCreatedAsTimestamp
})
.then(
() => console.log("success"),
(err) => console.error("failed", err);
);
Alternative export style
If you intend to add some utility functions to firebase.js, the way you import stuff changes slightly
import firebase from 'firebase/app';
import 'firebase/firestore';
/* ... */
// Initialize Firebase
export const defaultApp = firebase.initializeApp(firebaseConfig);
export function castToTimestamp(timestampLikeObject) {
const {_nanoseconds, _seconds} = timestampLikeObject;
return new firebase.firestore.Timestamp(_nanoseconds, _seconds);
}
export default firebase; // re-export firebase library & namespace as the default
With the above file, you would instead import it as:
// you can import this normally like in the other example, but we'll
// include some of the other exports (like the utility function)
import firebase, { castToTimestamp } from '../../firebase';
const {_nanoseconds, _seconds} = thoughts[lastItemIndex].date_created;
const dateCreatedAsTimestamp = new firebase.firestore.Timestamp(_nanoseconds, _seconds);
// OR
// const dateCreatedAsTimestamp = castToTimestamp(thoughts[lastItemIndex].date_created);
const db = firebase.firestore();
db.doc("collection/doc")
.set({
date_created: dateCreatedAsTimestamp
})
.then(
() => console.log("success"),
(err) => console.error("failed", err);
);
The following works:
export const timestamp = firebase.firestore.Timestamp;
let bob = new timestamp();
console.log("bob, bob);
NOTE:
firebase.firestore.Timestamp()
NOT
firebase.firestore().Timestamp()
https://firebase.google.com/docs/reference/js/firebase.firestore.Timestamp
I have this gtag (analytics plugin) that I can access on my components but never on my store.
I would appreciate any opinions. Thanks
plugins/vue-gtag.js
import Vue from "vue"
import VueGtag from "vue-gtag"
export default ({ app }, inject) => {
Vue.use(VueGtag, {
config: {
id: process.env.ga_stream_id
}
})
}
store/gaUserProperty.js
import Vue from "vue"
import { User } from "~/models/user/User"
export const states = () => ({})
const getterObjects = {}
const mutationObjects = {}
Object.keys(states).forEach(key => {
getterObjects[key] = state => state[key]
mutationObjects[key] = (state, value) => (state[key] = value)
})
export const state = () => states
export const getters = { ...getterObjects }
export const mutations = { ...mutationObjects }
export const actions = {
async sendUserProperties({ dispatch, commit }) {
let res = await this.$UserApi.getUser()
if (!(res instanceof User)) {
} else {
// I can access this on my components and pages but for some reason not here....
console.log(this.$gtag)
}
}
}
To import this properly, I would export the instance (or any of its internals) from main.(ts|js):
const Instance = new Vue({...whatever});
// only export what you need in other parts of the app
export const { $gtag, $store, $t, $http } = Instance;
// or export the entire Instance
export default Instance;
now you can import it in your store:
import Instance from '#/main';
// or:
import { $gtag } from '#/main';
// use Instance.$gtag or $gtag, depending on what you imported.
As other answers mentioned, in current Vuex version the Vue instance is available under this._vm inside the store. I'd refrain from relying on it, though, as it's not part of the exposed Vuex API and not documented anywhere. In other words, Vuex developers do not guarantee it will still be there in future versions.
To be even more specific, Vue's promise is that all v2 code will work in v3. But only if you use the exposed API's.
And the discussion here is not even on whether it will be removed or not (it most likely won't). To me, it's more a matter of principle: if it starts with a _ and it's not documented, it translates into a message from authors: "We're reserving the right to change this at any time, without warning!"
You can access the Vue instance through this._vm in the Vuex store, so you would just need to do:
console.log(this._vm.$gtag)
That should do the trick.
According to nuxtjs the plugins are available in the store actions.
https://nuxtjs.org/docs/directory-structure/plugins
Sometimes you want to make functions or values available across your
app. You can inject those variables into Vue instances (client side),
the context (server side) and even in the Vuex store. It is a
convention to prefix those functions with a $.
I have an AuthService class that provides all the api calls and handles authentication for those calls, so its a nice modular service. It is not a React component and not used in any render calls. It handles fetch calls mostly. In many other classes now, I use a single global instance of this class, and import it at the top.
I don't think a context is the right approach because it's not an object type or used in renders. I use the instance in componentDidMount() and useEffect().
an example:
//at the bottom, outside curly braces defining AuthService
export const Auth = new AuthService();
a consumer:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { useState, useEffect } from 'react';
import CommentList from "./CommentList";
import CommentForm from "./CommentForm";
import Comment from "./Comment";
import AuthService from './AuthService';
import { Auth } from './AuthService';
export default function CommentBox(props) {
const [comments, setComments] = useState([]);
// const Auth = new AuthService();
const [formText, setFormText] = useState('');
const [update, setUpdate] = useState(false);
useEffect(() => {
Auth.fetch('/comments/get_comment_for_bill/' + props.id + '/').then((data) => {
if (typeof data[0] !== 'undefined') {
setComments(data);
} else {
setComments([]);
}
setUpdate(false);
});
return () => {
return;
}
}, [props, update]);
return (
<div >
<CommentList comments={comments}/>
<CommentForm id={props.id} formText={formText} setFormText={setFormText} setUpdate={setUpdate}
onChange={e => {
setFormText(e.target.value);
}} />
</div>
);
}
I think the best approach to using singletons in React is by attaching it to the window object. Just make sure you first attach the singleton to an object, which in turn is attached to the window object - to avoid polluting your window namespace. You would do this attaching in your startup script only once:
index.js
var window.app = {}
window.app.authentication = (function() {
function authenticateUser(userId) {
}
return {
authenticateUser: authenticateUser
}
})();
Then in some other module where you want to use it:
Login.js
window.app.authentication.authenticateUser("johndoe");
It's just fine. There's nothing wrong. But why use same instance for everything?
new AuthService()
I would recommend you to export AuthService. Then, whenever you'll need to use that service, define a new instance and use:
const Auth = new AuthService()
// now, use Auth
It's just a personal choice.