Handling Firebase Authentication popup with NextJS to-do list app - javascript

I've built a todo app. I'm using nextjs as a framework, tailwind for css, and firebase to authenticate users to login via Google. I've got a component that adds todo items (called ToDoEntry.jsx), a component that shows the resulting todos which can be edited or deleted (called ToDoCard.jsx), and a Navbar component with a login and logout functionality called Navbar.jsx. All of these components show up on the same page called page.jsx, where ToDoCard.jsx and ToDoEntry.jsx only show up if I am logged via the Google login in from the navbar. I've also got a firebase configuration file called fbconfig.js, and a login authentication file called AuthHelper.js to help manage the user's login state.
The functionality worked JUST FINE before but I wanted to hide the ToDoEntry.jsx and ToDoCard.jsx components behind the login feature. I wanted to make it such page.jsx checks if the user is logged in before rendering those components.
Below is the last version of what the app looked like on vercel before I broke it:
https://todo-app-jet-pi.vercel.app/
My current problem is that after adding the AuthHelper.js and importing it into the Navbar.jsx, the login button in the navbar is no longer working, and the user is stuck on the main page. Clicking the button does not do anything, and the signInWithPopup method I used to have directly on the Navbar (but now in Authhelper.js) no longer works. I've pasted below the page.jsx, Navbar.jsx, fbconfig.js (minus the actual config details) and AuthHelper.js. Can someone tell me why the login button is not working?
P.S.: the firebase configuration and the login popup was working 100% just fine until I tried to hide components behind a login screen.
page.jsx
import ToDoEntry from "#/components/ToDoEntry";
import ToDoCard from "#/components/ToDoCard";
import Navbar from "#/components/Navbar";
import { useState } from "react";
export default function Home() {
const [loggedIn, setLoggedIn] = useState(false);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
return (
<div>
<Navbar setLoggedIn={setLoggedIn} setUser={setUser} />
{loading ? (
<div>Loading...</div>
) : loggedIn ? (
<>
<ToDoEntry user={user} />
<ToDoCard user={user} />
</>
) : (
<div className="flex flex-col items-center justify-top h-screen mt-10">
<h1 className="text-center text-4xl font-medium">
✏️ Welcome to{" "}
<span className="underline decoration-blue-500/30">
Walid&apos;s to-do list!
</span>{" "}
</h1>
<p className="mt-3 font-medium py-2 px-4">
Please login to continue.
</p>
</div>
)}
</div>
);
}
Navbar.jsx
import { FaGoogle, FaSignOutAlt } from "react-icons/fa";
import { auth } from "../app/fbconfig";
import { handleLogin } from "../app/AuthHelper";
export default function Navbar({ setLoggedIn, setUser }) {
const [user, setAuthUser] = useState(null);
useEffect(() => {
auth.onAuthStateChanged((user) => {
if (user) {
setAuthUser(user);
setLoggedIn(true);
setUser(user);
} else {
setAuthUser(null);
setLoggedIn(false);
setUser(null);
}
});
}, [setLoggedIn, setUser]);
const handleLogout = async () => {
try {
await auth.signOut();
setAuthUser(null);
setLoggedIn(false);
} catch (error) {
console.error(error);
}
};
const handleNavbarLogin = async () => {
const user = await handleLogin();
if (user) {
setAuthUser(user);
setLoggedIn(true);
setUser(user);
}
};
return (
<nav className="bg-blue-500">
<div className="flex p-4 justify-center">
{user ? (
<div className="flex">
<div className="flex items-center mr-3 text-white">
{user ? "Welcome, " + user.displayName : ""}
</div>
<button
className="flex bg-white text-blue-500 font-medium p-2 rounded-lg justify-items-center hover:bg-blue-800 hover:text-white transition-left duration-500 ease-in-out"
onClick={handleLogout}
>
<p className="mr-1">Logout</p>{" "}
<FaSignOutAlt className="my-auto" />
</button>
</div>
) : (
<button
className="flex bg-white text-blue-500 font-medium p-2 rounded-lg justify-items-center hover:bg-blue-800 hover:text-white transition-left duration-500 ease-in-out"
onClick={handleNavbarLogin}
>
<p className="mr-1">Login with</p> <FaGoogle className="my-auto" />
</button>
)}
</div>
</nav>
);
}
AuthHelper.js
const handleLogin = async () => {
try {
const result = await auth.signInWithPopup(provider);
return result.user;
} catch (error) {
console.error(error);
return null;
}
};
export { handleLogin };
fbconfig.js
import { getAuth, GoogleAuthProvider } from "firebase/auth";
const firebaseConfig = {
apiKey: "XXX",
authDomain: "XXX",
projectId: "XXX",
storageBucket: "XXX",
messagingSenderId: "XXX",
appId: "XXX",
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const provider = new GoogleAuthProvider();
export { auth, provider };
Here's the console error I get in runtime when I try to click login:
react_devtools_backend.js:4012 TypeError: _fbconfig__WEBPACK_IMPORTED_MODULE_0__.auth.signInWithPopup is not a function
at handleLogin (webpack-internal:///(:3000/app-client)/./app/AuthHelper.js:9:74)
at handleNavbarLogin (webpack-internal:///(:3000/app-client)/./components/Navbar.jsx:47:88)

The Authhelper.js was not working the way it should because I wrote
const result = await auth.signInWithPopup(provider);
instead of
const result = await signInWithPopup(auth, provider);
import { signInWithPopup } from "firebase/auth";
const handleLogin = async () => {
try {
const result = await signInWithPopup(auth, provider);
return result.user;
} catch (error) {
console.error(error);
return null;
}
};
export { handleLogin };

Related

How to make set on useffect change immediately? [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 16 days ago.
I make an ecommerce site
I am trying to display the page with the details of a product:
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import axios from "axios";
import Layout from "../components/Layout";
const ProductPage = () => {
const router = useRouter();
const [product, setProduct] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProduct = async () => {
try {
const response = await axios.get(`/api/products?id=${router.query.id}`);
const currentProduct = response.data;
setLoading(false);
setProduct(currentProduct);
} catch (error) {
console.error(error);
}
};
fetchProduct();
}, [router.query.id]);
return (
<Layout title={product.name}>
{loading ? (
<div className="text-center">Loading...</div>
) : (
<div className="max-w-lg mx-auto p-5">
<h1 className="text-2xl font-bold mb-5">{product.name}</h1>
<img
src={product.imageUrl}
alt={product.name}
className="w-full mb-5"
/>
<p className="mb-5">{product.description}</p>
<p className="text-xl font-bold mb-5">
Price: {product.price}
</p>
<Link href="/" legacyBehavior>
<a className="btn btn-primary">Go back to the products list</a>
</Link>
</div>
)}
</Layout>
);
};
export default ProductPage;
The values ​​are not displayed so I put some console.log
When I put a console.log in the "try" product returns an empty object, and when I put the console.log after the useffect the product returns an object with my values
You need to set the "isLoading" state in false whene the "product" state when the state has finished setting. In order to do that you need to add 1 more useEffect to listen for changes in the product state, for that you must add the "product" state as a dependency of the useEffect.
Try the next code:
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import axios from "axios";
import Layout from "../components/Layout";
const ProductPage = () => {
const router = useRouter();
const [product, setProduct] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProduct = async () => {
try {
const response = await axios.get(`/api/products?id=${router.query.id}`);
const currentProduct = response.data;
setProduct(currentProduct);
} catch (error) {
console.error(error);
}
};
fetchProduct();
}, [router.query.id]);
useEffect(() => {
setLoading(false);
}, [product]);
return (
<Layout title={product.name}>
{loading ? (
<div className="text-center">Loading...</div>
) : (
<div className="max-w-lg mx-auto p-5">
<h1 className="text-2xl font-bold mb-5">{product.name}</h1>
<img
src={product.imageUrl}
alt={product.name}
className="w-full mb-5"
/>
<p className="mb-5">{product.description}</p>
<p className="text-xl font-bold mb-5">Price: {product.price}</p>
<Link href="/" legacyBehavior>
<a className="btn btn-primary">Go back to the products list</a>
</Link>
</div>
)}
</Layout>
);
};
export default ProductPage;
useState hook does not update the state value instantly. There are a lot of things happening when you change the state, like batching state updates, recomputing DOM manipulations, re-rendering components, etc.
If you want to know what is going to be set as your state, log the argument that you are passing to the setProduct function. You may also use React dev tools.

Uncaught SyntaxError: The requested module '/src/components/Spinner.jsx' does not provide an export named 'Spinner

I have a spinner function component and im exporting that component as default. Then im importing it into userProfile file to display the loading animation based on the circumstances. But i keep getting this error:
Uncaught SyntaxError: The requested module '/src/components/Spinner.jsx' does not provide an export named 'Spinner
This is my Spinner.jsx file:
import {Circles} from "react-loader-spinner";
const Spinner = ({ message }) => {
return (
<div className="flex flex-col justify-center items-center w-full h-full ">
<Circles
color="#00BFFF"
height={50}
width={200}
className="m-5"
/>
<p className="text-lg text-center px-2">{message}</p>
</div>
);
};
export default Spinner;
This is file where im calling that Spinner Component:
import Spinner from "./Spinner.jsx";
const Userprofile = () => {
const [user, setUser] = useState(null);
const [pins, setPins] = useState(null);
const [text, setText] = useState("Created");
const [activeBtn, setActiveBtn] = useState("Created");
const navigate = useNavigate();
const { userId } = useParams();
if (!user) return <Spinner message="Loading Profile..." />;
return <div>Userprofile</div>;
};
export default Userprofile;

Nextjs Warning: Maximum update depth exceeded

I'm writing a react Header component and have added the functionality to click in and out of a dropdown menu. I'm getting the following error whenever I load the page:
next-dev.js?3515:20 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render
I imagine I'm making a mistake somewhere in a useEffect (I am running 2 useEffects, one inside a component and one inside another function), but am unsure where I'm going wrong. Here is the code below:
import React from "react";
import { useState, useEffect, useRef } from "react";
import { FaChevronDown, FaLastfmSquare } from "react-icons/all";
import { signOut } from "firebase/auth";
import { useRouter } from "next/router";
import { auth } from "../utils/auth";
function useOutsideAlerter() {
const [visible, setVisible] = useState(false);
const ref = useRef(null);
useEffect(() => {
const handleHideDropdown = (event) => {
if (event.key === "Escape") {
setVisible(false);
}
};
const handleClickOutside = (e) => {
if (ref.current && !ref.current.contains(e.target)) {
setVisible(false);
}
};
document.addEventListener("keydown", handleHideDropdown, true);
document.addEventListener("mousedown", handleClickOutside, true);
return () => {
document.removeEventListener("keydown", handleHideDropdown, true);
document.removeEventListener("mousedown", handleClickOutside, true);
};
}, []);
return { visible, ref, setVisible };
}
const Header = () => {
const [user, setUser] = useState(null);
const router = useRouter();
const { ref, visible, setVisible } = useOutsideAlerter();
useEffect(() => {
const user = localStorage.getItem("user");
if (user) {
setUser(JSON.parse(user));
}
});
const logUserOut = () => {
signOut(auth)
.then(() => {
localStorage.removeItem("user");
router.push("/");
})
.catch((error) => {
console.error(error);
alert("There was a problem signing out.");
});
};
return (
<div className="flex items-center justify-between p-4 border-b border-gray-300">
<h1 className={"font-bold text-lg"}>Matrice</h1>
<div ref={ref}>
<button
onClick={() => setVisible(!visible)}
className="hover:bg-gray-100 rounded-md p-2 flex items-center justify-center text-gray-400"
>
{user?.displayName || "Login"} <FaChevronDown className={"ml-2"} />
</button>
{visible && (
<div
className={
"absolute top-14 right-4 rounded-md border border-gray-300 bg-white overflow-hidden"
}
>
{user && (
<button
onClick={logUserOut}
className={
"text-left text-sm hover:bg-blue-700 hover:text-white w-full px-4 py-2"
}
>
Log Out
</button>
)}
<button
onClick={() =>
router.replace(
"mailto:email"
)
}
className={
"text-left text-sm hover:bg-blue-700 hover:text-white w-full px-4 py-2"
}
>
Request Feature
</button>
</div>
)}
</div>
</div>
);
};
export default Header;
I imagine I'm making a mistake somewhere in a useEffect
Yes, you haven't specified a dependency array for your useEffect() within your Header component:
useEffect(() => {
const user = localStorage.getItem("user");
if (user) {
setUser(JSON.parse(user));
}
}, []); // <--- added empty dependency array
Without a dependeency array, your useEffect() callback will run after every render/rerender. So in your current code, this happens:
React renders your JSX
Your useEffect() callback is called, calling setUser if the user key in your local storage is set
As setUser() was called with a new object reference (a new object is created when doing JSON.parse()), React sees this as an update and triggers a rerennder, and so we go back to step 1 above, causing an infinite loop.
By adding an empty dependency array [] to the useEffect() call, you're telling React to only call your function on the initial mount, and not subsequent rerenders.

How to add firebase authentication data to a firestore collection in React?

Currently, when a user signs up they are being created as a user in the firebase authentication. I am trying to add that newly created user directly into a firestore collection upon creation.
The following is the AuthContext.js
import React, { useContext, useState, useEffect } from "react";
import { auth } from "../firebase";
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
function signup (email, password) {
return auth.createUserWithEmailAndPassword(email, password);
}
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password);
}
function logout() {
return auth.signOut();
}
function resetPassword(email) {
return auth.sendPasswordResetEmail(email);
}
function updateEmail(email) {
return currentUser.updateEmail(email);
}
function updatePassword(password) {
return currentUser.updatePassword(password);
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}
The following is Signup.js
import React, { useRef, useState } from 'react'
import { Form, Button, Card, Alert } from "react-bootstrap"
import { Link, useHistory } from "react-router-dom"
import { useAuth } from './contexts/AuthContext'
export default function Signup() {
const emailRef = useRef()
const passwordRef = useRef()
const passwordConfirmRef = useRef()
const { signup } = useAuth()
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
const history = useHistory()
async function handleSubmit(e){
e.preventDefault()
if (passwordRef.current.value !==
passwordConfirmRef.current.value) {
return setError('Passwords do not match')
}
try{
setError('')
setLoading(true)
await signup(emailRef.current.value,passwordRef.current.value)
history.push("/")
} catch {
setError('Failed to create an account')
}
setLoading(false)
}
return (
<>
<Card>
<Card.Body>
<h2 className="text-center mb-4">Sign Up</h2>
{error && <Alert variant="danger">{error}</Alert>}
<Form onSubmit={handleSubmit}>
<Form.Group id="email">
<Form.Label>Email</Form.Label>
<Form.Control type="email" ref={emailRef} required />
</Form.Group>
<Form.Group id="password">
<Form.Label>Password</Form.Label>
<Form.Control type="password" ref={passwordRef} required />
</Form.Group>
<Form.Group id="password-confirm">
<Form.Label>Password Confirmation</Form.Label>
<Form.Control type="password" ref={passwordConfirmRef} required />
</Form.Group>
<Button disabled={loading} className="w-100" type="submit">
Sign Up
</Button>
</Form>
<div className="w-100 text-center mt-2">
Already have an account? <Link to="/login">Log In</Link>
</div>
</Card.Body>
</Card>
</>
)
}
Thank you in advance, any help will be greatly appreciated.
The creation of the user in Firebase Auth will happen no matter what you do, but you can take advantage of that by creating a Cloud Function that triggers everytime a user is created in Firebase auth and then create a new document in Firestore representing that user. Here is a Cloud Function example that does just that:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
exports.createUser = functions.auth.user().onCreate((user) => {
const { uid } = user;
const userCollection = db.collection('users');
userCollection.doc(uid).set({
someData: "123"
});
});
Also in the documentation you can see how to deploy Cloud Functions, in case you are not familiar with it.

how to show loader in react . using hooks

I am using axios for communicate with server.I want to show loader when user request to server and hide the loader when request is complete
So i make a custom component to do this task .but my UI hang when I click multiple times on same button
const Loader = () => {
const { loadingCount } = useLoadingState(),
{showLoading, hideLoading} = useLoadingActions();
useEffect(()=>{
const self = this
axios.interceptors.request.use(function (config) {
showLoading();
return config
}, function (error) {
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
// spinning hide
// self.props.loading(false)
hideLoading()
return response;
}, function (error) {
hideLoading();
return Promise.reject(error);
});
})
return (
<div>
{loadingCount > 0 ?<div style={{position:"fixed",display:"flex",justifyContent:"center",alignItems:"center",width:'100%',height:'100%',zIndex:999999}}>
{/*{loadingCount > 0 ? */}
<Spin tip="Loading..." style={{zIndex:999999}}></Spin>
{/*: null}*/}
</div>: null}
</div>
);
};
Problem is on useeffect
when I comment out useEffect code it works perfectly .
NoTe : showloading and hideloading increase and decrease the loading count.
I think I have deallocate axios object the when component is unmount.???
Add empty array to sencod parameter to useEffect.
It works like componentDidMount() in functional component.
const { useState, useEffect } = React;
const Counter = () => {
const [count, setCount] = useState(0)
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
setTimeout(() => {
setIsLoaded(true);
}, 3000);
}, []); // here
return (
<div>
{
isLoaded &&
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
}
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="app"></div>
i usualy use this code to show loading when request data is processing and hide when it's done
const Loader = () => {
const {data, setdata} = useState([])
useEffect(()=>{
axios.get('your host').then(res => {
setdata(res.data);
}).catch(err => {
setdata(res.data);
}
});
return (
<div>
{data.length > 0
?
<div style={{position:"fixed",display:"flex",justifyContent:"center",alignItems:"center",width:'100%',height:'100%',zIndex:999999}}> </div>
:
<Spin tip="Loading..." style= {{zIndex:999999}}>
</Spin>
</div>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
First I created Loading components in shared folder. I am using Daisy UI, that's why you have to first install tailwind & daisy otherwise it will not work. My Loading Code:
import React from 'react';
const Loading = () => {
return (
<div className="flex items-center justify-center ">
<div className="w-16 h-16 border-b-2 border-gray-900 rounded-full animate-spin"></div>
</div>
);
};
export default Loading;
Then I am using this Loading component in my Allproduct component. For viewing Loading i created Reload useState.You will see below in my code, that will help my loader show when fetching time is very long.
import React, { useEffect, useState } from 'react';
import Loading from '../Shared/Loading';
import AllProduct from './AllProduct';
const AllProducts = () => {
const [products, setProduct]=useState([])
const [Reload, setReload] = useState(true);
useEffect(()=>{
fetch('https://stormy-hamlet-97462.herokuapp.com/products/')
.then(res=>res.json())
.then(data=>{setProduct(data)
setReload(false)})
},[])
if(Reload){
return <Loading></Loading>
}
return (
<div>
<h4 className='text-4xl text-primary text-center sm:w-full px-32 mx-5
lg:my-12 '>All Products</h4>
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5'>
{
products.map(product=><AllProduct key={product._id} product={product} ></AllProduct>)
}
</div>
</div>
);
};
export default AllProducts;

Categories

Resources