How to use a firebase class in a js file - reactjs

I currently have a firebase file setup like so:
class Firebase {
constructor() {
app.initializeApp(firebaseConfig);
this.auth = app.auth();
this.db = app.database();
this.storage = app.storage();
}
// *** Auth API ***
doCreateUserWithEmailAndPassword = (email, password) =>
this.auth.createUserWithEmailAndPassword(email, password);
This is just part of the file. To access it I setup a context
export const withFirebase = Component => props => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
)
I then can wrap any component with withFirebase bada bing bada boom, it works.
However I'm trying out a redux type of library (react sweet state) which is pretty much a few js consts. In example:
const initialState = {
orgID: null,
memberID: 'blah here',
data: [],
}
const actions = {
fetchOrg: () => async ({ setState }) => {
const callFirebase = await Firebase.auth.onAuthStateChanged(authUser => {
if(authUser) {
//do some stuff
} else {
return false;
}
})
},
}
How can I use the firebase class instance in this setting?
I tried const firebaseAuth = new Firebase();
I get this error: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).

My solution to this ended up being just passing firebase as a prop to the action, which I believe is the "reacty" way of doing it:
const actions = {
fetchOrg: firebase => async ({ setState }) => {
const callFirebase = await Firebase.auth.onAuthStateChanged(authUser => {
if(authUser) {
//do some stuff
} else {
return false;
}
})
},
}

Related

How to create HOC for auth in Next.js?

I want to create basic Next.js HOC for authentication. I searched but I didn't figure it out.
I have an admin page in my Next.js app. I want to fetch from http://localhost:4000/user/me and that URL returns my user. If user data returns, component must be rendered. If data didn't return, I want to redirect to the /admin/login page.
I tried this code but that didn't work. How can I solve this issue? Also can I use useSWR instead of fetch?
const withAuth = (Component, { data }) => {
if (!data) {
return {
redirect: {
destination: "/admin/login",
},
};
}
return Component;
};
withAuth.getInitialProps = async () => {
const response = await fetch("http://localhost:4000/user/me");
const data = await response.json();
return { data };
};
export default withAuth;
const AdminHome = () => {
return ();
};
export default withAuth(AdminHome);
Server-side authentication
Based on the answer from Create a HOC (higher order component) for authentication in Next.js, you can create a re-usable higher-order function for the authentication logic.
If the user data isn't present it'll redirect to the login page. Otherwise, the function will continue on to call the wrapped getServerSideProps function, and will return the merged user data with the resulting props from the page.
export function withAuth(gssp) {
return async (context) => {
const response = await fetch('http://localhost:4000/user/me');
const data = await response.json();
if (!data) {
return {
redirect: {
destination: '/admin/login'
}
};
}
const gsspData = await gssp(context); // Run `getServerSideProps` to get page-specific data
// Pass page-specific props along with user data from `withAuth` to component
return {
props: {
...gsspData.props,
data
}
};
}
}
You can then use it on the AdminHome page to wrap the getServerSideProps function.
const AdminHome = ({ data }) => {
return ();
};
export const getServerSideProps = withAuth(context => {
// Your normal `getServerSideProps` code here
return { props: {} };
});
export default AdminHome;
Client-side authentication
If you'd rather have the authentication done on the client, you can create a higher-order component that wraps the component you want to protect.
const withAuth = (Component) => {
const AuthenticatedComponent = () => {
const router = useRouter();
const [data, setData] = useState()
useEffect(() => {
const getUser = async () => {
const response = await fetch('http://localhost:4000/user/me');
const userData = await response.json();
if (!userData) {
router.push('/admin/login');
} else {
setData(userData);
}
};
getUser();
}, []);
return !!data ? <Component data={data} /> : null; // Render whatever you want while the authentication occurs
};
return AuthenticatedComponent;
};
You can then use it to wrap the AdminHome component directly.
const AdminHome = () => {
return ();
};
export default withAuth(AdminHome);
If you're looking for the typescript version:
withAuth.ts
export function withAuth(gssp: GetServerSideProps): GetServerSideProps {
return async (context) => {
const { user } = (await getSession(context.req, context.res)) || {};
if (!user) {
return {
redirect: { statusCode: 302, destination: "/" },
};
}
const gsspData = await gssp(context);
if (!("props" in gsspData)) {
throw new Error("invalid getSSP result");
}
return {
props: {
...gsspData.props,
user,
},
};
};
}
Home.tsx
export const getServerSideProps = withAuth(async (context) => {
return { props: {} };
});

Alternative useState in react-native class component

i'm aware i cant use useState in class components, but there's this tutorial i'm trying to relpicate in my App. They used a functional component unlike me.
Their App.js in the tutorial is like this:
const App = () => {
useEffect(()=>{
fcmService.registerAppWithFCM();
fcmService.register(onRegister, onNotification, onOpenNotification);
localNotificationService.configure(onOpenNotification)
},[])
const onRegister = (token) => {
console.log("[App] Token", token);
}
const onNotification = (notify) => {
// console.log("[App] onNotification", notify);
const options = {
soundName: 'default',
playSound: true,
}
localNotificationService.showNotification(
0,
notify.notification.title,
notify.notification.body,
notify,
options,
)
}
const onOpenNotification = async (notify) => {
console.log('notify', notify);
}
So basically, const onOpenNotification, const onRegister, const onNotification are called in another file imported into App.js. I tried adding them in my App.js like :
class App extends Component {
componentDidMount() {
fcmService.registerAppWithFCM();
fcmService.register(onRegister, onNotification, onOpenNotification);
localNotificationService.configure(onOpenNotification)
const onRegister = (token) => {
console.log("[App] Token", token);
}
const onNotification = (notify) => {
// console.log("[App] onNotification", notify);
const options = {
soundName: 'default',
playSound: true,
}
localNotificationService.showNotification(
0,
notify.notification.title,
notify.notification.body,
notify,
options,
)
}
const onOpenNotification = async (notify) => {
console.log('notify', notify);
}
}
}
My console.log shows these errors:
LOG [FCMService] getInitialNotification getInitialNotification null
LOG [LocalNotificationService] onRegister: {"os": "android", "token": "emA0hq4KCMq0j:APA91bEWbOUXjxdIs_s2ksSbjwxhdMVfr35y9sZBUIYX72Q9obU7daQw4zI-a0qn6KsvxWvGtQoEdPlTq5l98trb-yhmtARDcliqAayi_r0K8f_"}
LOG [FCMService] getToken Rejected [TypeError: onRegister is not a function. (In 'onRegister(fcmToken)', 'onRegister' is undefined)]
I'm guessing that's because the const functions() are not properly structured, Whats the best way to replicate this tutorial, preferably if I don't have to change to a functional component
I don't think I would be able to put all the information you might need to help here, but I would appreciate if you could take some time to see how the 2 imported files I talked about are structured and how they call the const onRegister in App.js
Thank you!
EDIT: Updated Code
Try moving the methods to class level.
class App extends Component {
componentDidMount() {
fcmService.registerAppWithFCM();
fcmService.register(this.onRegister, this.onNotification, this.onOpenNotification);
localNotificationService.configure(this.onOpenNotification)
}
onRegister = (token) => {
console.log("[App] Token", token);
}
onNotification = (notify) => {
// console.log("[App] onNotification", notify);
const options = {
soundName: 'default',
playSound: true,
}
localNotificationService.showNotification(0,notify.notification.title,
notify.notification.body, notify, options,)
}
onOpenNotification = async (notify) => {
console.log('notify', notify);
}
}

add a counter of the length of documents of a collection in firebase

I'm developing an ecommerce where I use react in the front end and firebase/firestore in the backend.
I managed to add/delete products and I stored them in a collection that belongs to the each user. What I want to do is to have a counter in the navbar, and in other components, that indicates the number of documents (products) that the user that's login has in the basket, but because of the scope, I cannot manage to do it.
In products.js
const addToBasket = () => {
db.collection('users').doc(user?.uid).collection('basket').add({productId: id, title: title,
cont: content, img: image, price: price})
}
In checkoutProduct.js
const removeFromBasket = () => {
firestore().collection('users').doc(user?.uid).collection('basket').doc(id.toString()).delete();
}
In checkout.js
useEffect(() => {
if(user){
firestore().collection('users').doc(user?.uid).collection('basket').get().then(snapshot => {
setProducts(snapshot.docs.map((doc) => {return {...doc.data(), id: doc.id} }))
})
}
}, [])
You can get the number of documents from the QuerySnapshot that is returned from get():
firestore().collection('users').doc(user?.uid).collection('basket').get().then(snapshot => {
setProducts(snapshot.docs.map((doc) => {return {...doc.data(), id: doc.id} }))
setCartItemCount(snapshot.size); // <== this is the new line
})
This is a common thing that needs to be handled on the frontend.
One solution for you is to store the information in a context.
https://reactjs.org/docs/hooks-reference.html#usecontext
Conceptually, one thing you can do is to update that context whenever a successful write has been made to the database, in your case Firestore.
So, that way you update your frontend as the backend updates.
This can be done with an observer on your snapshot.
Read this: https://firebase.google.com/docs/firestore/query-data/listen
As Daniel said there are many ways to solve this problem. So lets elaborate a bit on his useContext proposal. They way I handle Firebase in my apps, I have a single class for the Firebase, because it should be a singleton:
class Firebase {
constructor() {
app.initializeApp(config);
this.db = app.database();
this.auth = app.auth();
}
// *** Auth API ***
createUserWithEmailAndPassword = (email, password) =>
this.auth.createUserWithEmailAndPassword(email, password);
signInWithEmailAndPassword = (email, password) =>
this.auth.signInWithEmailAndPassword(email, password);
signOut = () => this.auth.signOut();
sendPasswordResetEmail = email => this.auth.sendPasswordResetEmail(email);
updatePassword = password => this.auth.currentUser.updatePassword(password);
onAuthUserListener = (next, fallback) =>
this.auth.onAuthStateChanged(user => {
if (user) {
this.user(user.uid)
.once("value")
.then(snapshot => {
const dbUser = snapshot.val();
if (!dbUser.roles) {
dbUser.roles = {};
}
next({
uid: user.uid,
email: user.email,
...dbUser
});
});
} else {
fallback();
}
});
// *** User API ***
user = uid => this.db.ref(`/users/${uid}`);
users = () => this.db.ref(`users`);
}
export default Firebase;
Then I would create a context for it:
const FirebaseContext = React.createContext(null);
There are many ways how that context can be used, either with useContext, or with FirebaseContext.Provider/Consumer
I usually wrap my app in FirebaseProvider:
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'));
After that, anywhere within my component's tree I can use that context with a consumer or with useContext:
function BasketCount() {
const firebase = useContext(FirebaseContext);
const [count, setCount] = useState(0);
useEffect(() => {
firebase.firestore.collection('users').doc(user?.uid).collection('basket').get().then(snapshot => {
setCount(snapshot.size); // <== this is the new line
})
}, [firebase])
return (
<>{count}</>
)
}
You are more than welcome to take a look at this skeleton to see more things in context.

Handling auth with React Hooks

I've been chasing my tail for hours now trying to figure out how to handle auth on my component using firebase and react hooks.
I've created a custom useAuth hook that is intended to handle all the auth behaviors. My thought was to put a useEffect on the root of my component tree that would trigger if the firebase.auth.onAuthStateChanged() ever changed (ie, user is now logged out / logged in.) But, at this point after making a million unsuccessful changes I really don't know what I'm doing anymore.
Here is the code that I have...
RootPage component
const RootPage = ({ Component, pageProps }): JSX.Element => {
const { logoutUser, authStatus } = useAuth();
const router = useRouter();
useEffect(() => {
authStatus();
}, [authStatus]);
...
}
my thought was ok, lets trigger authStatus on mount, but that ends up with me lying about my dependencies. So, in an effort to not lie about my deps, I added authStatus to the deps. Logging out and then logging in results in this:
useAuth hook
const useAuth = () => {
const { fetchUser, resetUser, userData } = useUser();
const { currentUser } = firebaseAuth;
const registerUser = async (username, email, password) => {
try {
const credentials = await firebaseAuth.createUserWithEmailAndPassword(
email,
password
);
const { uid } = credentials.user;
await firebaseFirestore
.collection('users')
.doc(credentials.user.uid)
.set({
username,
points: 0,
words: 0,
followers: 0,
following: 0,
created: firebase.firestore.FieldValue.serverTimestamp(),
});
fetchUser(uid);
console.log('user registered', credentials);
} catch (error) {
console.error(error);
}
};
const loginUser = async (email, password) => {
try {
// login to firebase
await firebaseAuth.signInWithEmailAndPassword(email, password);
// take the current users id
const { uid } = firebaseAuth.currentUser;
// update the user in redux
fetchUser(uid);
} catch (error) {
console.error(error);
}
};
const logoutUser = async () => {
try {
// logout from firebase
await firebaseAuth.signOut();
// reset user state in redux
resetUser();
return;
} catch (error) {
console.error(error);
}
};
const authStatus = () => {
firebaseAuth.onAuthStateChanged((user) => {
if (user) {
console.log('User logged in.');
// On page refresh, if user persists (but redux state is lost), update user in redux
if (userData === initialUserState) {
console.log('triggered');
// update user in redux store with data from user collection
fetchUser(user.uid);
}
return;
}
console.log('User logged out.');
});
};
return { currentUser, registerUser, loginUser, logoutUser, authStatus };
};
export default useAuth;
I'm relatively certain that react hooks are only meant for reusable pieces of logic, so if the purpose of your hook is to contact firebase in every single component you're using it, along with rerendering and refreshing state every time that component is updated, then it's fine, but you can't use hooks for storing global auth state, which is how auth should be stored.
You're looking for react context instead.
import React, {createContext, useContext, useState, useEffect, ReactNode} from 'react'
const getJwt = () => localStorage.getItem('jwt') || ''
const setJwt = (jwt: string) => localStorage.setItem('jwt', jwt)
const getUser = () => JSON.parse(localStorage.getItem('user') || 'null')
const setUser = (user: object) => localStorage.setItem('user', JSON.stringify(user))
const logout = () => localStorage.clear()
const AuthContext = createContext({
jwt: '',
setJwt: setJwt,
user: {},
setUser: setUser,
loading: false,
setLoading: (loading: boolean) => {},
authenticate: (jwt: string, user: object) => {},
logout: () => {},
})
export const useAuth = () => useContext(AuthContext)
const Auth = ({children}: {children: ReactNode}) => {
const auth = useAuth()
const [jwt, updateJwt] = useState(auth.jwt)
const [user, updateUser] = useState(auth.user)
const [loading, setLoading] = useState(false)
useEffect(() => {
updateJwt(getJwt())
updateUser(getUser())
}, [])
const value = {
jwt: jwt,
setJwt: (jwt: string) => {
setJwt(jwt)
updateJwt(jwt)
},
user: user,
setUser: (user: object) => {
setUser(user)
updateUser(user)
},
loading: loading,
setLoading: setLoading,
authenticate: (jwt: string, user: object) => {
setJwt(jwt)
updateJwt(jwt)
setUser(user)
updateUser(user)
},
logout: () => {
localStorage.removeItem('jwt')
localStorage.removeItem('user')
updateJwt('')
updateUser({})
setLoading(false)
},
}
return <AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
}
export default Auth
...
// app.tsx
import Auth from './auth'
...
<Auth>
<Router/>
</Auth>
// or something like that
...
import {useAuth} from './auth'
// in any component to pull auth from global context state
You can change that according to whatever you need.
I know the issue why its happening but don't know the solution...But i am not fully sure...Look how react works is if any parents re render it also cause re render the children..ok?Its mean if any reason your apps is re rendering and the useAuth keep firing...so for this there to much console log.But i am not sure that it will work or not..give me your repo i will try on my local computer
const RootPage = ({ Component, pageProps }): JSX.Element => {
const { logoutUser, authStatus,currentUser } = useAuth();
const router = useRouter();
useEffect(() => {
authStatus();
}, [currentUser]);
//only fire when currentUser change
...
}
Update your useEffect hook like so:
useEffect(() => {
const unsub = firebaseAuth.onAuthStateChanged((user) => {
if (user) {
console.log('User logged in.');
// On page refresh, if user persists (but redux state is lost), update user in redux
if (userData === initialUserState) {
console.log('triggered');
// update user in redux store with data from user collection
fetchUser(user.uid);
}
} else {
console.log('User logged out.');
}
});
return ()=> unsub;
},[])

Converting a class based component to hooks (gapi API)

I have this class based component using the gapi (Google Auth) API that renders a button and it works:
import React from 'react';
class GoogleAuth extends React.Component {
state = { isSignedIn: null };
componentDidMount() {
window.gapi.load('client:auth2', () => {
window.gapi.client
.init({
clientId: process.env.REACT_APP_CLIENT_ID,
scope: 'email',
})
.then(() => {
this.auth = window.gapi.auth2.getAuthInstance();
this.handleAuthChange();
this.auth.isSignedIn.listen(this.handleAuthChange);
});
});
}
handleAuthChange = () => {
this.setState({ isSignedIn: this.auth.isSignedIn.get() });
};
handleSignIn = () => {
this.auth.signIn();
};
handleSignOut = () => {
this.auth.signOut();
};
renderAuthButton() {
if (this.state.isSignedIn === null) {
return null;
} else if (this.state.isSignedIn) {
return <button onClick={this.handleSignOut}>Sign Out</button>;
} else {
return <button onClick={this.handleSignIn}>Sign in with Google</button>;
}
}
render() {
return <div>{this.renderAuthButton()}</div>;
}
}
export default GoogleAuth;
I'm having a tough time trying to convert this to use hooks. The main issue is this.auth... That's how the class has a reference to window.gapi.auth2.getAuthInstance()
I have tried many different ways including keeping auth in state like:
export default function GoogleAuth() {
const [isSignedIn, setIsSignedIn] = useState(null);
const [auth, setAuth] = useState(null);
useEffect(() => {
window.gapi.load('client:auth2', () => {
window.gapi.client
.init({
clientId: process.env.REACT_APP_CLIENT_ID,
scope: 'email',
})
.then(() => {
setAuth(window.gapi.auth2.getAuthInstance());
setIsSignedIn(auth.isSignedIn.get());
auth.isSignedIn.listen(() => setIsSignedIn(auth.isSignedIn.get()));
});
});
}, [auth]);
It's only 8 months later but try useRef with auth like below. It works for me.
const GoogleAuth = () => {
const [isSignedIn, setSignedIn] = useState(null)
const auth = useRef(null);
useEffect(() => {
window.gapi.load('client:auth2', () => {
window.gapi.client.init({
clientId:
'jcu.apps.googleusercontent.com',
scope: 'email'
}).then(() => {
auth.current = window.gapi.auth2.getAuthInstance();
setSignedIn(auth.current.isSignedIn.get());
auth.current.isSignedIn.listen(onAuthChange)
});
});
}, [isSignedIn]);
const onAuthChange = () => {
setSignedIn(auth.current.isSignedIn.get())
}
if (isSignedIn === null) {
return (
<div>I don't know if we are signed in!</div>
);
} else if ( isSignedIn ){
return (
<div>I am signed in!</div>
);
} else {
return ( <div>I am not signed in. :(</div>);
}
}
Couple issues - you're referencing auth immediately after you're setting the state - auth won't be set until it re-renders with its new state.
I'm playing with similar code, and I had to resort to utilizing window.gapi in the initial setup to properly access the returned auth instance.
I imagine it may throw an error if a user clicks quickly they could catch it before auth is set, but I found the sign in/out functions to be able to handle this.
I also found it easiest to test in Incognito, as cookies and cacheing of the api seemed to create an unpredictable local testing environment.
My current component state
Just line in useEffect after auth.current = ...
setSignedIn(auth.current.isSignedIn.get()); is de facto function onAuthChange so call it like that:
.then(() => {
auth.current = window.gapi.auth2.getAuthInstance();
onAuthChange();
auth.current.isSignedIn.listen(onAuthChange);
});

Resources