React state update memory leak due to an unmounted component - reactjs

My app uses firebase to authenticate. During the sign-in process, I get the "Can't perform a React state update on an unmounted component" and it recommends using a cleanup function in a useEffect. I thought I was cleaning up the function in async function with the
finally {
setLoading(false);
}
Any help would be appreciated. Code below:
import React, { useState, useContext } from "react";
import styled from "styled-components/native";
import { Image, Text, StyleSheet } from "react-native";
import { FirebaseContext } from "../context/FirebaseContext";
import { UserContext } from "../context/UserContext";
export default function SignInScreen() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const firebase = useContext(FirebaseContext);
const [_, setUser] = useContext(UserContext);
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
setLoading(false);
}
};
return (
<Container>
<Main>
<Text style={styles.welcomeText}>Welcome</Text>
</Main>
<Auth>
<AuthContainer>
<AuthTitle>Email Address</AuthTitle>
<AuthField
autoCapitalize="none"
autoCompleteType="email"
autoCorrect={false}
autoFocus={true}
keyboardType="email-address"
onChangeText={(email) => setEmail(email.trim())}
value={email}
/>
</AuthContainer>
<AuthContainer>
<AuthTitle>Password</AuthTitle>
<AuthField
autoCapitalize="none"
autoCompleteType="password"
autoCorrect={false}
autoFocus={true}
secureTextEntry={true}
onChangeText={(password) => setPassword(password.trim())}
value={password}
/>
</AuthContainer>
</Auth>
<SignInContainer onPress={signIn} disabled={loading}>
{loading ? <Loading /> : <Text style={styles.text}>Sign In</Text>}
</SignInContainer>
<HeaderGraphic>
<Image
source={require("../images/heritage-films-logo.png")}
style={{ height: 150, width: 300, resizeMode: "contain" }}
/>
</HeaderGraphic>
</Container>
);
}

You should check if the component is still mounted before calling setState in some way. It's a typical React leakage issue. You can implement isMounted variable with useRef hook for that, despite the fact that the authors of React call it an anti-pattern, since you should cancel your async routines when the component unmounts.
function Component() {
const isMounted = React.useRef(true);
React.useEffect(() => () => (isMounted.current = false), []);
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
isMounted.current && setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
isMounted.current && setLoading(false);
}
};
}
Or another a bit magic way:
import { useAsyncCallback, E_REASON_UNMOUNTED } from "use-async-effect2";
import { CanceledError } from "c-promise2";
export default function SignInScreen() {
//...
const signIn = useAsyncCallback(function*() {
setLoading(true);
try {
yield firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = yield firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
setLoading(false);
} catch (error) {
CanceledError.rethrow(error, E_REASON_UNMOUNTED);
setLoading(false);
alert(error.message);
}
}, []);
return (<YourJSX onPress={signIn}>);
}

Related

React Context not getting updated on first click

Hi I am trying to learn and implement react context. I have encountered a bug when tryin to update user context.
I have a login page and when a user logs in, the email address and the user name should be updated in the user context.
When I click on submit, null information is stored in the context. But, when I click on submit the second time, I can see that the context is getting updated.
authentication.component.jsx
import Navigation from "../navigation/navigation.component";
import { Button, InputGroup, Form } from "react-bootstrap";
import { UserContext, setUser, setEmail } from "../../contexts/user.context";
import { useContext, useEffect, useState } from "react";
import axios from "axios";
import React from "react";
import "./authentication.styles.scss";
const UserLogon = () => {
const { setUser, setEmail } = useContext(UserContext);
const [emailAddr, setEmailAddr] = useState("");
const [password, setPassword] = useState("");
useEffect(() => {
//console.log(emailAddr);
}, [emailAddr, password]);
const updateFormData = () => {
setEmailAddr(emailAddr);
setPassword(password);
console.log("updated");
console.log(emailAddr);
console.log(password);
};
const saveEmail = (event) => {
setEmailAddr(event.target.value);
//console.log(emailAddr);
};
const savePassword = (event) => {
setPassword(event.target.value);
//console.log(password);
};
const verifyUserHandler = (event) => {
event.preventDefault();
const baseURL = `http://localhost:4000/verify_user?email=${emailAddr}&password=${password}`;
axios
.post(baseURL)
.then((response) => {
//console.log("User verified");
if (response.data[2] === "verified") {
console.log("user verified");
var email = response.data[0];
var name = response.data[1];
console.log("email: ", email);
console.log("name: ", name);
setEmail(email);
setUser(name);
} else {
console.log("user auth error");
}
})
.catch((e) => {
console.log(e);
});
};
return (
<div className="auth-container">
<div className="auth-login">
<div className="login-info">
<Form>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control
type="email"
placeholder="Enter email"
onChange={saveEmail}
/>
<Form.Text className="text-muted">
We'll never share your email with anyone else.
</Form.Text>
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Password"
onChange={savePassword}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicCheckbox">
<Form.Check type="checkbox" label="Check me out" />
</Form.Group>
<Button variant="primary" type="submit" onClick={verifyUserHandler}>
Submit
</Button>
</Form>
</div>
</div>
</div>
);
};
export default UserLogon;
user.context.jsx
import { createContext, useState, useEffect } from "react";
export const UserContext = createContext({
currentUser: null,
setCurrentUser: () => null,
userEmail: null,
setUserEmail: () => null,
});
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [userEmail, setUserEmail] = useState(null);
const setUser = (user) => {
console.log("USER: user context before", currentUser);
setCurrentUser(user);
console.log("USER: user context after", currentUser);
};
const setEmail = (email) => {
console.log("EMAIL: user context before", userEmail);
setUserEmail(email);
console.log("EMAIL: user context after", userEmail);
};
const value = { setUser, setEmail, currentUser, userEmail };
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
Any help is appreciated.
thank you.
The React state setter function is async which means that at the moment you console.log it's not yet updated in your state : )
Try this to validate:
const setUser = (user) => {
setCurrentUser(user)
console.log({ currentUser, user })
// To better debug wrap vars in { }
}
const setEmail = (email) => {
setUserEmail(email)
console.log({ userEmail, email })
}
More info from docs: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Try removing the intitial values from createContext. I think it might be causing your issue.
import { createContext, useState, useEffect } from "react";
export const UserContext = createContext();
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [userEmail, setUserEmail] = useState(null);
const setUser = (user) => {
setCurrentUser(user);
console.log(currentUser);
};
const setEmail = (email) => {
setUserEmail(email);
console.log(userEmail);
};
const value = { setUser, setEmail, currentUser, userEmail };
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
Add useEffect console log to context provider to check when the state is changing.
useEffect(() => {
console.log("User", currentUser);
console.log("Email", userEmail);
}, [currentUser, userEmail]);
As you can see from here it works properly

Function setDoc() called with invalid data. Unsupported field value: a custom UserImpl object (found in field owner in document CreatedClasses)

This is the first time I'm asking a question here and also a newbie to coding. I'm trying to clone google classroom.
I am trying to use firestore to make a db collection when creating the class. But when I click create it doesn't create the class and create the db in firestore. It shows that the setDoc() function is invalid. Im using firestore version 9 (modular)
Here is my Form.js file. (The firestore related code is also included here)
import { DialogActions, TextField , Button} from "#material-ui/core"
import React, {useState} from 'react'
import { useLocalContext, useAuth } from '../../../context/AuthContext'
import { v4 as uuidV4 } from 'uuid'
import { db} from '../../../firebase'
import { collection, doc, setDoc } from "firebase/firestore"
const Form = () => {
const [className, setClassName] = useState('')
const [Level, setLevel] = useState('')
const [Batch, setBatch] = useState('')
const [Institute, setInstitute] = useState('')
const {setCreateClassDialog} = useLocalContext();
const {currentUser} = useAuth();
const addClass = async (e) => {
e.preventDefault()
const id = uuidV4()
// Add a new document with a generated id
const createClasses = doc(collection(db, 'CreatedClasses'));
await setDoc(createClasses, {
owner:currentUser,
className: className,
level: Level,
batch: Batch,
institute: Institute,
id: id
}).then (() => {
setCreateClassDialog(false);
})
}
return (
<div className='form'>
<p className="class__title">Create Class</p>
<div className='form__inputs'>
<TextField
id="filled-basic"
label="Class Name (Required)"
className="form__input"
variant="filled"
value={className}
onChange={(e) => setClassName(e.target.value)}
/>
<TextField
id="filled-basic"
label="Level/Semester (Required)"
className="form__input"
variant="filled"
value={Level}
onChange={(e) => setLevel(e.target.value)}
/>
<TextField
id="filled-basic"
label="Batch (Required)"
className="form__input"
variant="filled"
value={Batch}
onChange={(e) => setBatch(e.target.value)}
/>
<TextField
id="filled-basic"
label="Institute Name"
className="form__input"
variant="filled"
value={Institute}
onChange={(e) => setInstitute(e.target.value)}
/>
</div>
<DialogActions>
<Button onClick={addClass} color='primary'>
Create
</Button>
</DialogActions>
</div>
)
}
export default Form
And also (I don't know whether this is helpful but my context file is below)
import React, { createContext, useContext, useEffect, useState } from "react";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
onAuthStateChanged,
signOut,
GoogleAuthProvider,
signInWithPopup
} from "firebase/auth";
import { auth } from "../firebase";
const AuthContext = createContext();
const AddContext = createContext()
export function useAuth() {
return useContext(AuthContext);
}
export function useLocalContext(){
return useContext(AddContext)
}
export function ContextProvider({children}){
const [createClassDialog,setCreateClassDialog] = useState(false);
const [joinClassDialog, setJoinClassDialog] = useState(false);
const value = { createClassDialog, setCreateClassDialog, joinClassDialog, setJoinClassDialog };
return <AddContext.Provider value={value}> {children} </AddContext.Provider>;
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true)
function signup(email, password) {
return createUserWithEmailAndPassword(auth,email, password);
}
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
function logout() {
return signOut(auth);
}
function resetPassword(email) {
return auth.sendPasswordResetEmail(email)
}
function googleSignIn() {
const googleAuthProvider = new GoogleAuthProvider();
return signInWithPopup(auth, googleAuthProvider);
}
function updateEmail(email) {
return currentUser.updateEmail(email)
}
function updatePassword(password) {
return currentUser.updatePassword(password)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged( auth, (user) => {
setCurrentUser(user);
setLoading(false)
});
return () => {
unsubscribe();
};
}, []);
return (
<AuthContext.Provider
value={{ currentUser, login, signup, logout, googleSignIn, resetPassword,updateEmail, updatePassword }}
>
{!loading && children}
</AuthContext.Provider>
);
}
The console error message:
Try something like this, excluding the collection function from setting the document.
// Add a new document with a generated id
await setDoc(doc(db, 'CreatedClasses'), {
owner:currentUser,
className: className,
level: Level,
batch: Batch,
institute: Institute,
id: classId
}).then (() => {
setCreateClassDialog(false);
})

Firebase Error TypeError: Cannot create property '_canInitEmulator' on string 'k#k.fr'

I m trying to implement an Firebase 9 auth system on my ReactJS project. Unfornutually I have one error like this when I m trying to auth with Email and Password.
I use also Login with google account and It work fine. I try to do some adjustments but always the same error append. I know how I can fix this.
Ask if u have more question about my code ;)
Uncaught (in promise) TypeError: Cannot create property '_canInitEmulator' on string 'k#k.fr'
at _performFetchWithErrorHandling (index.ts:131:1)
at _performApiRequest (index.ts:89:1)
at _performSignInRequest (index.ts:189:1)
at signInWithPassword (email_and_password.ts:45:1)
at EmailAuthCredential._getIdTokenResponse (email.ts:116:1)
at _processCredentialSavingMfaContextIfNecessary (mfa_error.ts:72:1)
at _signInWithCredential (credential.ts:37:1)
at signInWithCredential (credential.ts:69:1)
at signInWithEmailAndPassword (email_and_password.ts:267:1)
at onClick (Login.js:41:1)
Do you have any idea ?
init-firebase.js
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const googleProvider = new GoogleAuthProvider();
const signInWithGoogle = async () => {
try {
const res = await signInWithPopup(auth, googleProvider);
const user = res.user;
const q = query(collection(db, "users"), where("uid", "==", user.uid));
const docs = await getDocs(q);
if (docs.docs.length === 0) {
await addDoc(collection(db, "users"), {
uid: user.uid,
name: user.displayName,
authProvider: "google",
email: user.email,
});
}
} catch (err) {
console.error(err);
alert(err.message);
}
};
const logInWithEmailAndPassword = async (email, password) => {
try {
await signInWithEmailAndPassword(auth, email, password);
} catch (err) {
console.error(err);
alert(err.message);
}
};
const registerWithEmailAndPassword = async (name, email, password) => {
try {
const res = await createUserWithEmailAndPassword(auth, email, password);
const user = res.user;
await addDoc(collection(db, "users"), {
uid: user.uid,
name,
authProvider: "local",
email,
});
} catch (err) {
console.error(err);
alert(err.message);
}
};
const sendPasswordReset = async (email) => {
try {
await sendPasswordResetEmail(auth, email);
alert("Password reset link sent!");
} catch (err) {
console.error(err);
alert(err.message);
}
};
const logout = () => {
signOut(auth);
};
export {
auth,
db,
signInWithGoogle,
logInWithEmailAndPassword,
registerWithEmailAndPassword,
sendPasswordReset,
logout,
signInWithEmailAndPassword,
};
Login.js
import React, { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { auth, signInWithEmailAndPassword, signInWithGoogle } from "../lib/init-firebase";
import { useAuthState } from "react-firebase-hooks/auth";
function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [user, loading, error] = useAuthState(auth);
const navigate = useNavigate();
useEffect(() => {
if (loading) {
// maybe trigger a loading screen
return;
}
if (user) navigate("/dashboard");
}, [user, loading]);
return (
<div className="body-size">
<input
type="text"
className="login__textBox"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="E-mail Address"
/>
<input
type="password"
className="login__textBox"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button
className="login__btn"
onClick={() => signInWithEmailAndPassword(email, password)}
>
Login
</button>
</div>
</div>
</div>
);
}
export default Login;

React Query: InvalidateQuery not working to update users list

I have a simple app that has a form and list. Currently, I am using query client.InvalidateQueries to update the users' list after submitting the form. As the documentation says, using InvalidateQuery will trigger refetching, but somehow I had not seen an update to the list after adding users. Am I missing something?
Add User
import React, { useState } from 'react';
import { useFormik } from 'formik';
import Input from '../../elements/Input/Input';
import * as Yup from 'yup';
import { QueryClient, useMutation } from 'react-query';
import axios from 'axios';
const queryClient = new QueryClient();
const CreateItemView = () => {
function gen4() {
return Math.random().toString(16).slice(-4);
}
function generateID(prefix) {
return (prefix || '').concat([gen4(), gen4(), gen4(), gen4(), gen4(), gen4(), gen4(), gen4()].join(''));
}
const mutation = useMutation(
(formData) => {
axios.post('http://localhost:8000/users', formData).then((response) => console.log(response));
},
{
onSuccess: () => {
queryClient.invalidateQueries('users');
},
},
);
const [data, setData] = useState([]);
const initialValues = {
id: '',
name: '',
email: '',
channel: '',
};
const onSubmit = (values, { resetForm }) => {
setData([...data, values]);
const ID = generateID().toString();
values.id = ID;
mutation.mutate(values);
resetForm();
};
const validationSchema = Yup.object({
name: Yup.string().required('Required!'),
email: Yup.string().email('Invalid format').required('Required!'),
channel: Yup.string().required('Required!'),
});
const formik = useFormik({
initialValues,
onSubmit,
validationSchema,
});
return (
<div>
<form onSubmit={formik.handleSubmit}>
<Input type={'text'} name={'name'} id={'name'} label={'Name'} formik={formik} />
<Input type={'email'} name={'email'} id={'email'} label={'Email'} formik={formik} />
<Input type={'text'} name={'channel'} id={'channel'} label={'channel'} formik={formik} />
<button type="submit">Submit</button>
</form>
</div>
);
};
export default CreateItemView;
User's list
import React from 'react';
import ListView from './ListView';
import { useQuery } from 'react-query';
import axios from 'axios';
const getUsers = async () => {
const response = await axios.get('http://localhost:8000/users');
return response.data;
};
const ListContainer = () => {
const { data, isLoading, isFetching } = useQuery('users', getUsers);
console.log('list', data);
return <div>{isFetching ? 'loading...' : <ListView dataSource={data} />}</div>;
};
export default ListContainer;
You have to return the fetch function in the mutation. The onSuccess handler will fire when the promise is resolved.
const mutation = useMutation(
formData => {
return axios.post('http://localhost:8000/users', formData)
.then((response) => console.log(response));
},
{
onSuccess: () => {
queryClient.invalidateQueries('users');
},
},
);
i think the solution for your problem is to replace this :
onSuccess: () => {
queryClient.invalidateQueries('users');
},
By this :
onSettled:() => {
queryClient.invalidateQueries('users');
},
you should use the same instance of queryClient from the root of your app which is accessible via the useQueryClient() hook.
Hence you should be doing const queryClient = useQueryClient() instead of generating new instance with const queryClient = new QueryClient().

React: Best way to pass Firestore info to components?

Can't figure out an efficient way to set a user's profile and then pass that data onwards to other components as needed.
Below is an example of my current logic, and although the app works, the rendering is not efficient. When I click on various parts of my app, the data coming from my UserProfile component is re-rendering every time causing the text to change from the initial text to the rendered data text.
The main issue, I believe, is the communication between the UserProfile and Dashboard Home snippets below. I'm new to the useEffect logic, so I would imagine the inefficient setup is with that hook.
Any help or nudge in the right direction is appreciated!
Thanks
AuthContext file => Setting the current user
import React, { useContext, useState, useEffect } from 'react';
import firebase from 'firebase/app';
import {
auth,
signInWithGoogle,
createUserProfileDocument,
firestore,
} from '../firebase.utils';
const AuthContext = React.createContext();
export const useAuth = () => {
return useContext(AuthContext);
};
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
const signup = (email, password) => {
return auth.createUserWithEmailAndPassword(email, password);
};
const login = (email, password) => {
return auth.signInWithEmailAndPassword(email, password);
};
const logout = () => {
setCurrentUser(null);
return auth.signOut();
};
const resetPassword = email => {
return auth.sendPasswordResetEmail(email);
};
const updateEmail = email => {
return currentUser.updateEmail(email);
};
const updatePassword = password => {
return currentUser.updatePassword(password);
};
const deleteProfile = () => {
currentUser.delete();
firestore.doc(`users/${currentUser.uid}`).delete();
};
const updateName = displayName => {
return currentUser.updateProfile({
displayName: displayName,
});
};
const setName = displayName => {
return auth.currentUser.updateProfile({
displayName: displayName,
});
};
const googleSignIn = () => {
const google = signInWithGoogle();
setCurrentUser(google);
return google;
};
const updatePersonalSettings = data => {
createUserProfileDocument(currentUser, data);
};
const updateAccountSettings = data => {
createUserProfileDocument(currentUser, data);
};
console.log(currentUser);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword,
updateName,
setName,
googleSignIn,
updatePersonalSettings,
updateAccountSettings,
deleteProfile,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
UserProfile file => Setting the userInfo
import { useState, useEffect } from 'react';
import { useAuth } from '../context/auth-context';
import { createUserProfileDocument } from '../firebase.utils';
const UserProfile = () => {
const { currentUser } = useAuth();
const [userInfo, setUserInfo] = useState();
const [loading, setLoading] = useState(true);
const setUserData = async () => {
if (currentUser) {
const userRef = await createUserProfileDocument(currentUser);
userRef.onSnapshot(doc => {
setUserInfo({
id: doc.id,
...doc.data(),
});
});
}
};
useEffect(() => {
setUserData();
}, []);
return { userInfo };
};
export default UserProfile;
Dashboard home file => Example of rendering data from the UserProfile component
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import sprite from '../../../../assets/sprite.svg';
import UserProfile from '../../../../user-profile/user-profile';
import './home-dashboard.styles.scss';
const HomeDashboard = () => {
const { userInfo } = UserProfile();
const handleCurrentLevel = () => {
return !userInfo || userInfo.currentLevel === undefined ? (
<h1>Welcome! Start your eval to see your level</h1>
) : (
<h1>Current Level: {userInfo.currentLevel}</h1>
);
};
const handleCurrentLevelCard = () => {
return !userInfo || userInfo.currentLevel === undefined
? 'Start a new eval to see your level'
: `You scored a ${userInfo.currentLevel} in your last eval`;
};
return (
<div className="home-dash">
<div className="home-dash__title">{handleCurrentLevel()}</div>
<div className="home-dash__cards">
<div className="home-dash__card-1">
<svg className="icon home-dash__card-icon">
<use href={sprite + '#card-icon-success'}></use>
</svg>
<h3 className="home-dash__card-title">
Latest Eval Results
</h3>
<div className="home-dash__card-result-text">
{handleCurrentLevelCard()}
</div>
<Link to="/eval-quiz">
<button className="home-dash__card-btn--purple">
Start New Eval
</button>
</Link>
</div>
{/* TODO Add resutls to firestore */}
{
<div className="home-dash__card-2">
<svg className="icon home-dash__card-icon">
<use href={sprite + '#card-icon-lightbulb'}></use>
</svg>
<h3 className="home-dash__card-title">
Areas to practice
</h3>
<div className="home-dash__card-result-text">
We recommend working on reading skills
</div>
{/*<button className="home-dash__card-btn--blue">
Practice
</button>*/}
<div className="home-dash__coming-soon">
Coming soon!
</div>
</div>
}
</div>
</div>
);
};
export default HomeDashboard;
Firestore/Firebase setup
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
const app = firebase.initializeApp({
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER,
appId: process.env.REACT_APP_FIREBASE_APP_ID,
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID,
});
export const createUserProfileDocument = async (userAuth, additionalData) => {
if (!userAuth) return;
const userRef = firestore.doc(`users/${userAuth.uid}`);
const snapShot = await userRef.get();
const { displayName, email, photoURL } = userAuth;
const createdAt = new Date();
if (!snapShot.exists) {
console.log(displayName);
try {
await userRef.set({
displayName,
photoURL,
email,
createdAt,
...additionalData,
});
} catch (error) {
console.log('error catching data', error.message);
}
}
if (snapShot.exists) {
try {
await userRef.update({
displayName,
email,
...additionalData,
});
} catch (error) {
console.log('error catching data', error.message);
}
}
return userRef;
};
export const firestore = firebase.firestore();
const googleProvider = new firebase.auth.GoogleAuthProvider();
googleProvider.setCustomParameters({ prompt: 'select_account' });
export const signInWithGoogle = () => auth.signInWithPopup(googleProvider);
export const auth = app.auth();
export default app;
Consider this answer a draft, I've made it public in case you can piece it together but I'll update it with some documentation in the morning.
import { useState, useEffect } from 'react';
import { useAuth } from '../context/auth-context';
import { createUserProfileDocument } from '../firebase.utils';
const UserProfile = () => {
const { currentUser } = useAuth();
const [userInfo, setUserInfo] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!currentUser) {
// user is signed out
setUserInfo(null);
setLoading(false);
return;
}
const userRef = /* ... */;
setUserInfo(undefined); // clear stale data
setLoading(true); // update loading status
return userRef.onSnapshot({
next(snapshot) {
if (!snapshot.exists) {
userRef
.set({
/* new data */
})
.catch((err) => {
// TODO: Handle errors
});
return;
}
setUserInfo({
id: snapshot.id,
...snapshot.data(),
});
setLoading(false);
},
error(err) {
// TODO: Handle errors
}
});
}, [currentUser]);
return { userInfo }; // <-- this seems odd? missing loading?
};
export default UserProfile;

Resources