Firebase createUserProfileDocument() is returning undefined - javascript

the createUserProfileDocument() method from firebase is returning undefined for some reason, therefore, it's throwing an error on my broswer's console.
The error thrown is:
App.js:23 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'onSnapshot') at App.js:23:1
the code:
import React from 'react';
import './App.css';
import { connect } from 'react-redux';
import { Switch, Route } from 'react-router-dom';
import HomePage from './pages/homepage/homepage.component';
import ShopPage from './pages/shop/shop.component';
import Header from './components/header/header.componet';
import { auth, createUserProfileDocument } from './firebase/firebase.utils';
import SignInAndSignUpPage from './pages/sign-in-and-sign-up/sign-in-and-sign-up.component';
import { setCurrentUser } from './redux/user/user.actions';
class App extends React.Component {
unsubscribeFromAuth = null;
componentDidMount() {
const { setCurrentUser } = this.props;
this.unsubscribeFromAuth = auth.onAuthStateChanged(async (userAuth) => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
console.log(userRef);
userRef.onSnapshot((snapshot) => {
setCurrentUser({
id: snapshot.id,
...snapshot.data(),
});
});
}
setCurrentUser(userAuth);
});
}
componentWillUnmount() {
this.unsubscribeFromAuth();
}
render() {
return (
<div className='App'>
<Header />
<Switch>
<Route exact path='/' component={HomePage} />
<Route exact path='/shop' component={ShopPage} />
<Route
exact
path='/signin'
component={SignInAndSignUpPage}
/>
</Switch>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: (user) => dispatch(setCurrentUser(user)),
});
export default connect(null, mapDispatchToProps)(App);
The portion of code related to my error is the following:
componentDidMount() {
const { setCurrentUser } = this.props;
this.unsubscribeFromAuth = auth.onAuthStateChanged(async (userAuth) => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
console.log(userRef);
userRef.onSnapshot((snapshot) => { // This is Line 23......
setCurrentUser({
id: snapshot.id,
...snapshot.data(),
});
});
}
setCurrentUser(userAuth);
});
}
I console.logged(userRef) as you can see, and it returns undefined. This is my first experience with firebase so I am not sure where the error is.
I googled and found an answer here in stackoverflow which is alike to my issue: TypeError: Cannot read properties of undefined (reading 'onSnapshot')
However, that problem is specifically related to how the developer was passing the method as an array and not a method, which does not help my situation.
EDIT: Adding firebase.utils code.
import firebase from 'firebase/compat/app';
import 'firebase/compat/firestore';
import 'firebase/compat/auth';
const config = {
apiKey: 'AIzaSyDxUMY8sUpOMAoxEyHo41ONilzjL2woWHk',
authDomain: 'crown-db-d4416.firebaseapp.com',
projectId: 'crown-db-d4416',
storageBucket: 'crown-db-d4416.appspot.com',
messagingSenderId: '887003208895',
appId: '1:887003208895:web:778f8f55a0ad2c72953a0a',
measurementId: 'G-6TGCRVN7D3',
};
export const createUserProfileDocument = async (userAuth, additionalData) => {
if (!userAuth) return;
const userRef = firestore.doc(`users/${userAuth.uid}`);
const snapShot = await userRef.get();
console.log(snapShot);
if (!snapShot.exists) {
const { displayName, email } = userAuth;
const createdAt = new Date();
try {
await userRef.set({
displayName,
email,
createdAt,
...additionalData,
});
} catch (error) {
console.log('Error creating user: ', error.message);
}
}
};
firebase.initializeApp(config);
export const auth = firebase.auth();
export const firestore = firebase.firestore();
const googleProvider = new firebase.auth.GoogleAuthProvider();
export const signInWithGoogle = () => {
auth
.signInWithPopup(googleProvider)
.then((res) => {
console.log(res.user);
})
.catch((error) => {
console.log(error.message);
});
};
export default firebase;

Your createUserProfileDocument function doesn't return anything, so that explains why this code then fails:
const userRef = await createUserProfileDocument(userAuth);
console.log(userRef);
userRef.onSnapshot((snapshot) => {
The solution is to return the userRef from createUserProfileDocument as your calling code expects:
export const createUserProfileDocument = async (userAuth, additionalData) => {
...
return userRef;
};

Related

how i set firebase display name when account is create

I try this away to set and get name for my users when account is registered but the value returns undefined. My auth context:
I am rookie sorry for the simples errors and sorry for my bad english
import React, { useContext, useState, useEffect, createContext } from "react";
import { auth, updateProfile } from "../firebase";
const AuthContext = createContext({});
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
function signup(email, name, password) {
return auth.createUserWithEmailAndPassword(email, password).then((res) => {
res.currentUser.updateProfile({
displayName: name,
});
});
}
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password);
}
function logout() {
return auth.signOut();
}
function resetPassword(email) {
return auth.sendPasswordResetEmail(email);
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
login,
signup,
logout,
resetPassword,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}
My Firebase Config:
import firebase from 'firebase/compat/app'
import 'firebase/compat/auth'
import { getFirestore } from 'firebase/firestore'
const app = firebase.initializeApp({
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
})
export const auth = app.auth()
const db = getFirestore()
export default app
export { db }
I want to set the displayName function when user is registered with form in register page.

Next13 not working with existing firebase auth context

I am building an app using next13 (to make use of server side components), however, for some reason my existing AuthContext is not working. I am getting the following error:
TypeError: React.createContext is not a function
From what I can see, the AuthContext needs to be set to 'use client', as there is use of useState and useEffect within it, but for some reason the application no longer recognises that createContext is actually a function.
This is my AuthContext:
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { onAuthStateChanged, signOut, signInWithEmailAndPassword, createUserWithEmailAndPassword } from 'firebase/auth';
import { auth } from '../config';
const AuthContext = createContext({});
export const useAuth = () => useContext(AuthContext);
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setLoading(true);
setUser(user ?? null);
setLoading(false);
});
return () => unsubscribe();
}, []);
const login = async (email, password) => {
await signInWithEmailAndPassword(auth, email, password);
};
const logout = async () => {
setUser(null);
await signOut(auth)
};
const register = async (email, password) => {
try {
const userCred = await createUserWithEmailAndPassword(auth, email, password);
await userCred.user.sendEmailVerification({
url: process.env.NEXT_PUBLIC_HOST
});
} catch (err) {
return {
errorCode,
errorMessage
}
}
};
return (
<AuthContext.Provider value={{ user, loading, login, logout, register }}>
{children}
</AuthContext.Provider>
);
};
The AuthContext is then used in my main layout page within the app directory:
'use client';
import { CssBaseline, Container } from '#mui/material';
import { NavBar, Footer } from '../components';
import { AuthContextProvider } from '../context';
import '#fontsource/roboto/300.css';
import '#fontsource/roboto/400.css';
import '#fontsource/roboto/500.css';
import '#fontsource/roboto/700.css';
const RootLayout = ({ children }) => {
return (
<html lang='en'>
<head>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<AuthContextProvider>
<CssBaseline />
<NavBar />
<Container component='main' sx={{ padding: 3 }}>
{children}
</Container>
<Footer />
</AuthContextProvider>
</body>
</html>
);
}
export default RootLayout;
I am unsure if I need to take a different approach to authentication, perhaps using the next-auth package, but I am not sure what the best way would be.
Cheers for any help!
Here's an example of useContext I am using on my application.
'use client'
import { createContext, useContext, useEffect, useState } from 'react'
import { getAuth, User } from 'firebase/auth'
import { initializeApp, getApps, getApp } from 'firebase/app'
import nookies from 'nookies'
const firebaseConfig = {
...
}
getApps().length ? getApp() : initializeApp(firebaseConfig)
const auth = getAuth()
const AuthContext = createContext<User | null>(null)
export function AuthProvider({ children }: any) {
//
const [user, setUser] = useState<User | null>(null)
useEffect(() => {
return auth.onIdTokenChanged(async (user) => {
if (!user) {
setUser(null)
nookies.set(undefined, 'token', '', { path: '/' })
} else {
const token = await user.getIdToken()
setUser(user)
nookies.set(undefined, 'token', token, { path: '/' })
}
})
}, [])
useEffect(() => {
const handle = setInterval(async () => {
const user = auth.currentUser
if (user) await user.getIdToken(true)
}, 15 * 60 * 1000)
return () => clearInterval(handle)
}, [])
return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>
}
export const useAuth = () => {
return useContext(AuthContext)
}
Note that we're also forcing token refresh every 15 minutes, and saving that to cookies. You can access cookies in server pages using the new next13 cookies package.
You can also get the user by importing the useAuth hook we just created.
For example
'use client'
import useAuth from '../context/AuthProvider'
const Page = () => {
const {user} = useAuth()
// Rest of your application
}
Hope it helps

While I enter data through a form and submit , firebase gets the image but details of other data didn't get on the firestore collections

Image can be seen on the storage of firebase .Error is Cannot read properties of undefined (reading 'name') at submitPortfolio .But it fails to add data.Once a error in the line const storageRef = ref(storage, `portfolio/${image.name}`); seems .But now it didn't.I don't know why!
firebase.js
import { initializeApp } from "firebase/app";
import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
import { getStorage } from 'firebase/storage';
import { getFirestore } from 'firebase/firestore/lite';
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: ",
storageBucket: "",
messagingSenderId: "",
appId: ""
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth();
const provider = new GoogleAuthProvider();
export const db = getFirestore(app);
export const storage = getStorage(app);
export const signInWithGoogle = () => signInWithPopup(auth, provider);
login.js
import { signInWithGoogle } from '../../firebase';
import React from 'react'
const Login = () => {
return (
<div className="dashboard">
<button onClick={signInWithGoogle}>
Sign in with google
</button>
</div>
)
}
export default Login;
index.js
import { useEffect, useState } from "react";
import { getAuth, onAuthStateChanged } from 'firebase/auth';
import Home from "./home";
import Login from '../Login';
import React from 'react';
const Dashboard = () => {
const [user, setUser] = useState(null);
const auth = getAuth();
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if(user) {
setUser(user);
} else {
setUser(null);
}
})
}, []);
return (
<div>
{user ? <Home /> : <Login />}
</div>
)
}
export default Dashboard;
Home.js
import { useRef } from 'react';
import { auth, storage, db } from '../../firebase';
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import { addDoc } from 'firebase/firestore';
import { collection } from 'firebase/firestore/lite';
const Home = () => {
const form = useRef();
const submitPortfolio = (e) => {
e.preventDefault();
const name = form.current[0]?.value;
const description = form.current[1]?.value;
const url = form.current[2]?.value;
const image = form.current[3]?.files[0];
const storageRef = ref(storage, `portfolio/${image.name}`);
uploadBytes(storageRef, image).then(
(snapshot) => {
getDownloadURL(snapshot.ref).then((downloadUrl) => {
savePortfolio({
name,
description,
url,
image: downloadUrl
})
}, (error) => {
console.log(error);
savePortfolio({
name,
description,
url,
image: null
})
})
}, (error) => {
console.log(error);
savePortfolio({
name,
description,
url,
image: null
})
}
)
}
const savePortfolio = async (portfolio) => {
console.log(portfolio)
try {
await addDoc(collection(db, 'portfolio'), portfolio);
window.location.reload(false);
} catch (error) {
alert('Failed to add portfolio');
}
}
return (
<div className="dashboard">
<form ref={form} onSubmit={submitPortfolio}>
<p><input type="text" placeholder="Name" /></p>
<p><textarea placeholder="Description" /></p>
<p><input type="text" placeholder="Url" /></p>
<p><input type="file" placeholder="Image" /></p>
<button type="submit">Submit</button>
<button onClick={() => auth.signOut()}>Sign out</button>
</form>
</div>
)
}
export default Home;
I think you are using the wrong variable for the storageRef. You declared the variable name but use image.name
Change in submitProtfiolio the storageRef to:
const storageRef = ref(storage, `portfolio/${name}`);

produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'

I got following error at onAuthStateChanged method at store.dispatch(userActions.setUser(null));
Error: [Immer] produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '[object Object]'
I am trying to change to firebase authentication from jwt. So custom jwt authentication is using redux. Now when I call firebase's signOut(auth) method, onAuthStateChanged method give me this error. May I know how to write immerable object for user.
user_reducer.js
import produce from "immer";
import * as Action_Constants from "../actions/user_actions";
const initialState = null;
export const UserReducer = produce((state = initialState, action) => {
switch (action.type) {
case Action_Constants.SET_USER: {
return action.user;
}
case Action_Constants.FETCH_USER_COMPLETED: {
return action.user;
}
default:
return state;
}
});
user_actions.js
export const FETCH_USER = "FETCH_USER";
export const FETCH_USER_COMPLETED = "FETCH_USER_COMPLETED";
export const SET_USER = "SET_USER";
export const actionCreators = {
fetchUser: (id) => ({
type: FETCH_USER,
id,
}),
fetchUserCompleted: (user) => ({
type: FETCH_USER_COMPLETED,
user,
}),
setUser: (user) => ({
type: SET_USER,
user,
}),
};
I have deleted other firebase functions to simply the file.
auth_provider.jsx
import React, { useState, useEffect, useContext, createContext } from "react";
import { useLocation, Navigate } from "react-router-dom";
import { signIn, signUp } from "../helpers/gql_auth_helpers";
import paths from "../routes/paths";
import { store } from "../store/configure_store";
import { actionCreators as userActions } from "../store/actions/user_actions";
import { auth } from "../helpers/init-firebase";
import {
onAuthStateChanged,
signOut,
} from "firebase/auth";
const AuthContext = createContext(null);
let accessToken = "";
export const getAccessToken = () => accessToken;
export const setAccessToken = (token) => {
accessToken = token;
};
export const AuthProvider = ({ user: usr, children }) => {
const [user, setUser] = useState(usr);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setUser(user);
setAccessToken(user.getIdToken(true));
store.dispatch(userActions.setUser(user));
} else {
setUser(null);
setAccessToken(null);
store.dispatch(userActions.setUser(null));
}
});
return () => {
unsubscribe();
};
}, []);
async function logout() {
return signOut(auth);
}
const value = {
user,
accessToken,
logout,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
return useContext(AuthContext);
};

Using react with cloud firestore

I am trying to figure out how to get cloud firestore working with a react app.
I have found this tutorial, which uses react with realtime database, and have gotten it to load. Now I'm trying to figure out what changes I need to make to get it working with cloud firestore.
In my firebase.js, I have:
import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
import firestore from "firebase/firestore";
class Firebase {
constructor() {
app.initializeApp(config).firestore();
this.auth = app.auth();
// this.db = app.firebase.database()
this.db = app.firebase.firestore();
}
doCreateUserWithEmailAndPassword = (email, password) =>
this.auth.createUserWithEmailAndPassword(email, password);
doSignInWithEmailAndPassword = (email, password) =>
this.auth.signInWithEmailAndPassword(email, password);
doSignOut = () =>
this.auth.signOut();
doPasswordReset = email =>
this.auth.sendPasswordResetEmail(email);
doPasswordUpdate = password =>
this.auth.currentUser.updatePassword(password);
// *** User API ***
user = uid => this.db.ref(`users/${uid}`);
users = () => this.db.ref('users');
}
export default Firebase;
Then, in my form, I'm trying this:
import { withFirebase } from '../../../components/firebase';
onSubmit = event => {
const { username, email, passwordOne } = this.state;
this.props.firebase
.doCreateUserWithEmailAndPassword(email, passwordOne)
.then(authUser => {
// Create a user
return this.props.firebase
.user(authUser.user.uid)
.set({
username,
email,
});
})
.then(authUser => {
this.setState({ ...INITIAL_STATE });
this.props.history.push(ROUTES.INITIAL_PROFILE);
})
.catch(error => {
this.setState({ error });
});
event.preventDefault();
}
onChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
The import withFirebase has:
import FirebaseContext, { withFirebase } from './Context';
import Firebase from '../../firebase.1';
export default Firebase;
export { FirebaseContext, withFirebase };
FirebaseContext has:
import React from 'react';
const FirebaseContext = React.createContext(null);
export const withFirebase = Component => props => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
export default FirebaseContext;
When I try this, I get an error that says:
TypeError: Cannot read property 'firestore' of undefined
The error message points to this line of the firebase config file:
this.db = app.firebase.firestore();
How can I get firestore working in a react app?
Change this:
this.db = app.firebase.firestore();
into this:
this.db = app.firestore();
initializeApp() returns type App, and inside App you can find the method firestore():
https://firebase.google.com/docs/reference/js/firebase.app.App.html#firestore

Categories

Resources