I'm developing a REACT JS app and I've a doubt. If I have something that must not be taken as a dependency i usually put a let like in this case, where I'm initializing Firebase for my app:
import { initializeApp } from 'firebase/app';
import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';
let app = null;
const useInitializeFirebase = () => {
if (app) {
return;
}
app = initializeApp(window.FIREBASE);
initializeAppCheck(app, {
provider: new ReCaptchaV3Provider(window.RECAPTCHA_SITE_KEY),
isTokenAutoRefreshEnabled: true,
});
};
export default useInitializeFirebase;
The so exported hook is called within the main index.js file in this way:
const App = () => {
useInitializeFirebase();
...
}
So if the hook gets called multiple times (e.g. for a re-render), Firebase won't give error.
Is this the best approach? Or maybe I should use useRef or useState or even something else?
EDIT
The problem occurs also if I don't use an hook.
Take this code as reference:
import { initializeApp } from 'firebase/app';
let firebaseApp = null;
export default (firebaseConfig) => {
if (firebaseApp === null) {
firebaseApp = initializeApp(firebaseConfig);
}
return firebaseApp;
};
That can be used in this way
import { getAuth, signInWithCustomToken } from 'firebase/auth';
import firebaseApp from '#Src/helpers/firebaseApp';
const firebaseLogin = (token) => {
const auth = getAuth(firebaseApp(window.FIREBASE));
return signInWithCustomToken(auth, token);
};
export { firebaseLogin };
In this way if I don't put a guard firebase would be initialized multiple times, giving error.
Related
I set up my React project to use firebase auth using the modular v9 SDK like so. I now would like to create other hooks like useAnalytics, useFirestore, etc, that will allow me to access those utilities in components that need them. But if I use this same pattern for other firebase services, I will end up having to wrap my app with several contexts.
So instead of this auth provider component, I'm thinking of replacing it with a FirebaseProvider component that will wrap everything, but I am not sure if that is correct, or how I would integrate it with the existing auth code below.
import React, {useState, useContext, useEffect, createContext } from "react"
import {
getAuth,
signOut,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
} from "firebase/auth";
// I need to move this elsewhere like index.js
import { initializeApp } from "firebase/app";
firebase.initializeApp(<app config object>);
const auth = getAuth();
const authContext = createContext();
export const useAuth = () => {
return useContext()
}
// my App component is wrapped with this JSX element
export const ProvideAuth = ({children}) => {
const auth = useProvideAuth();
return <authContext.Provider value={auth}></authContext.Provider>
}
const useProvideAuth = () => {
const [user, setUser] = useState(null)
const signIn = (email, password) => {
signInWithEmailAndPassword(auth, email, password).then((res) => {
setUser(res.user)
})
}
const createUser = ...
const signOut = ...;
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
// remove listener on unmount
return () => unsubscribe();
}, []);
return {user, signIn, signOut, createUser};
}
I tried placing initializeApp in the index.js, but that code never seems to run and causes the authentication to fail.
I can't firebase v9 enableIndexedDbPersistence in a nextjs pwa.
this erros are trow
index.js // firebase main
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import { getFirestore } from 'firebase/firestore'
import { getStorage } from 'firebase/storage'
const firebaseConfig =
process.env.NEXT_PUBLIC_FIREBASE_CONFIG
const app = initializeApp(JSON.parse(firebaseConfig))
export const db = getFirestore(app)
// enableIndexedDbPersistence(db) // >>> if put here it said can't be invoked after getFirestore or any other funtion
export const auth = getAuth(app)
export const storage = getStorage(app)
So where should i invoke
The enableIndexedDbPersistence(db) function must be called before . From its documentation:
Must be called before any other functions (other than
{#link initializeFirestore}, {#link (getFirestore:1)} or
{#link clearIndexedDbPersistence}.
So getFirestore(app) is actually exempted from that, in contrast what you stated in the comment of your code snipped:
// >>> if put here it said can't be invoked after getFirestore or any other function
Consequently, my guess would be that you might use the exported db before the promise of enableIndexedDbPersistence(db) completes.
You could resolve this in different ways, such as wrapping it in a service or method and make sure you are await-ing the promise, or generally change your app that db is not used right away.
In my Ionic PWA app, I'm successfully using the following:
import { getFirestore, enableIndexedDbPersistence } from "firebase/firestore";
import { initializeApp } from "firebase/app";
const firebaseConfig = {
// ...
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
enableIndexedDbPersistence(db)
.then(() => console.log("Enabled offline persistence"))
.catch((error) => {
if (error.code == "failed-precondition") {
// Multiple tabs open, persistence can only be enabled
// in one tab at a a time.
// ...
} else if (error.code == "unimplemented") {
// The current browser does not support all of the
// features required to enable persistence
// ...
}
});
This is very similar to your snipped indeed. But the first access to Firestore happens after via user interaction, and not right away.
In my project under plugins dir I have a single plugins called firebase.ts and it's look like this
import { defineNuxtPlugin } from "#app";
import { firebaseConfig } from "#/firebaseConfig";
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
export default defineNuxtPlugin((nuxtApp) => {
// Initialize Firebase
const firebaseApp = initializeApp(firebaseConfig);
const firebaseAuthInstance = getAuth(firebaseApp);
})
Whenever I run my project it's give this kind of error. But if I make the plugin client only i mean firebase.client.ts then it work just fine. But I want to get this pluin both in client and server side. How to achive that?
[h3] [unhandled] H3Error: Component auth has not been registered yet
at createError (file:///home/riyad/Desktop/nuxt-test/node_modules/h3/dist/index.mjs:238:15)
at Server.nodeHandler (file:///home/riyad/Desktop/nuxt-test/node_modules/h3/dist/index.mjs:428:21) {
statusCode: 500,
fatal: false,
unhandled: true,
statusMessage: 'Internal Server Error'
}
[nuxt] [request error] Component auth has not been registered yet
at createError (./node_modules/h3/dist/index.mjs:238:15)
at Server.nodeHandler (./node_modules/h3/dist/index.mjs:428:21)
Firebase libraries use browser cache to store data on client side, so you cannot load some of their libraries on server side. Auth library is used and works only on client site, same as all lib's with no /lite end. To be able to use library Firestore on server, import all functions from 'firebase/firestore/lite' file path.
Example of plugin:
import { defineNuxtPlugin } from '#app'
import { initializeApp, getApps } from 'firebase/app'
import { connectFirestoreEmulator as connectFirestoreEmulatorServer, getFirestore as getFirestoreServer } from 'firebase/firestore/lite'
import { connectAuthEmulator, getAuth } from "firebase/auth"
import { connectDatabaseEmulator, getDatabase } from "firebase/database"
import { connectFirestoreEmulator, getFirestore } from "firebase/firestore"
import { connectFunctionsEmulator, getFunctions } from "firebase/functions"
import { connectStorageEmulator, getStorage } from "firebase/storage"
export default defineNuxtPlugin((nuxtApp) => {
const firebaseConfig = { ...useRuntimeConfig().public.firebaseConfig }
if (!getApps().length) {
initializeApp(firebaseConfig)
}
// Code bellow allows to work with local emulators you can delete it if you work without emulators
const firestoreHost = getFirestoreServer().toJSON()['settings'].host
if(process.dev && firestoreHost !== 'localhost:8080') {
connectFirestoreEmulatorServer(getFirestoreServer(), 'localhost', 8080)
}
if (process.dev && process.client) {
connectAuthEmulator(getAuth(), "http://localhost:9099")
connectFirestoreEmulator(getFirestore(), "localhost", 8080)
connectDatabaseEmulator(getDatabase(), "localhost", 9000)
connectStorageEmulator(getStorage(), "localhost", 9199)
connectFunctionsEmulator(getFunctions(), "localhost", 5001)
}
})
Usage:
<template>
<div>
<button #click="signIn">Sign In</button>
<p>{{ data.test.title }}</p>
<p>User email: {{userEmail}}</p>
</div>
</template>
<script setup lang="ts">
import { getAuth, OAuthProvider, signInWithPopup } from 'firebase/auth';
import { doc, getDoc, getFirestore } from 'firebase/firestore/lite';
const data = reactive({ test: null })
const userEmail = ref('')
const result = await getDoc(doc(getFirestore(), 'test/test'))
if (result.exists()) data.test = result.data()
if (process.client) {
const userCredentials = await signInWithPopup(getAuth(), new OAuthProvider('google.com'))
if (userCredentials.user) userEmail.value = userCredentials.user.email
}
async function signIn() {
const userCredentials = await signInWithPopup(getAuth(), new OAuthProvider('google.com'))
if (userCredentials.user) userEmail.value = userCredentials.user.email
}
</script>
Code directly in setup will run on server and client. Function signIn and if (process.client) will run only on client same as life cycle hooks like onMounted()
After many attempts, I was able to solve the issue. Here is the code on the directory /plugins/plugins.ts:
import { getAuth } from "#firebase/auth";
import { initializeApp } from "firebase/app";
import { firebaseConfig } from "./firebase/firebaseConfig";
const authInstance = () => {
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
return auth
};
export default defineNuxtPlugin(() => {
return {
provide: {
authInstance,
},
};
});
It appears that the initializeApp Function was not called correctly. But if you call the initializeApp and getAuth on a new function it returns The Auth Object correctly.
I am new in vite. I am trying to initialize the firebase app. but I am getting errors like below
Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp()
I created a file name firebase.ts but i am not really sure where can i include this to initialize firebase globally.
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useHead } from '#vueuse/head'
import { isDark } from '/#src/state/darkModeState'
import useNotyf from '/#src/composable/useNotyf'
import sleep from '/#src/utils/sleep'
import { getAuth, signInWithEmailAndPassword } from '#firebase/auth'
const isLoading = ref(false)
const router = useRouter()
const notif = useNotyf()
const username = ref('')
const password = ref('')
const handleLogin = async () => {
if (!isLoading.value) {
isLoading.value = true
signInWithEmailAndPassword(getAuth(), username.value, password.value)
.then((user) => {
isLoading.value = false
router.push({ name: 'sidebar-dashboards-course' })
})
.catch((err) => {
isLoading.value = false
notif.error(
'There is no user record corresponding to this identifier. The user may be deleted'
)
})
}
}
Any solution appreciated!
In your firebase.ts, you are initializing Firebase using the compat version (that let's you use the name-spaced syntax even in V9) but you are trying to use the Modular version in your Vue components. Instead, try initializing Firebase using modular SDK as well. So the firebase.ts should look like:
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";
const firebaseConfig = {...};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const storage = getStorage(app);
export { auth, db, storage };
Then try importing these instances in your components instead of using get[Service]() functions again.
import { auth } from "../path/to/firebase.ts" // update the path as per your dir structure
// usage: instead of getAuth() here
await signInWithEmailAndPassword(auth, username.value, password.value)
Follow the official example to export your own useStore, and then use it in the component.
import { createStore, Store, useStore as baseUseStore } from 'vuex';
export const key: InjectionKey<Store<RootState>> = Symbol();
export function useStore() {
return baseUseStore(key);
}
use in the component
setup() {
const store = useStore();
const onClick = () => {
console.log(store)
store.dispatch('user/getUserInfo');
}
return {
onClick,
}
},
After running, store is undefined.
It can be obtained normally when I use it in the methods attribute
methods: {
login() {
this.$store.dispatch('user/getToken')
}
}
why? how to fix it
In that simplifying useStore usage tutorial, you still need to register the store and key in main.ts as they did. You will get undefined if you don't do this:
// main.ts
import { store, key } from './store'
const app = createApp({ ... })
// pass the injection key
app.use(store, key)
The reason is that baseUseStore(key) has no meaning until that's done.