i am trying to build a login page and when the create account button is clicked in the login /signup page, checking the authentication of the user, it should return to the home page.
But it is showing the error
import React, { useState } from "react";
import "./Login.css";
import { Link, useHistory } from "react-router-dom";
import { auth } from "./firebase";
function Login() {
const history = useHistory;
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const signIn = (e) => {
e.preventDefault();
};
const register = (e) => {
e.preventDefault();
auth
.createUserWithEmailAndPassword(email, password)
.then((auth) => {
console.log(auth);
if (auth) {
history.push("/");
}
})
Just call the useHistory hook like this:
const history = useHistory()
The useHistory hook gives you access to the history instance that you may use to navigate.
How to use:
import { useHistory } from "react-router-dom";
function HomeButton() {
let history = useHistory();
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
Related
I'm trying to implement authentication in a react app using the context API and Firebase's auth service and I'm halfway through, except for the fact that I'm not getting the desired results.
For example, if I click on the submit button without typing anything, it's supposed to throw an auth error of invalid email and show a post error message to firebase in the console. The same goes for if I type a password that's less than 6 characters. But, as it is, nothing is working. No error's showing in the console either, so, I can't seem to figure out where the problem is. I attached my app.js and UserAuthContext.js files below.
My SignUp code
import { useState } from "react";
import { useUserAuth } from "./context/UserAuthContext";
import { UserAuthContextProvider } from "./context/UserAuthContext";
function SignUp() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError ] = useState("")
const {signUp} = useUserAuth ();
const handleSubmit = async (e) => {
e.preventDefault();
try{
await signUp(email, password);
} catch (err){
}
}
// React code omitted
}
and my UserAuthContext.js file:
import { createContext, useContext, useState, useEffect } from "react";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
} from "firebase/auth";
import { auth } from "../firebase";
const userAuthContext = createContext();
export function UserAuthContextProvider({ children }) {
const [user, setUser] = useState("");
function signUp(email, password) {
return createUserWithEmailAndPassword(email, password);
}
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => {
unsubscribe();
};
}, []);
return (
<userAuthContext.Provider value={{ user, signUp }}>
{children}
</userAuthContext.Provider>
);
}
export function useUserAuth() {
return useContext(userAuthContext);
}
I attached my firebase.js file, just in case
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
// Settings for init app
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export default app;
After some questions, I think I understand your problem, when calling the createUserWithEmailAndPassword function, you are forgetting to pass the auth instance as the first parameter.
I suggest you make the following changes to the SignUp.
Note that I am using the function directly from firebase and not from the React Context.
import {
auth
} from "../firebase";
import {
createUserWithEmailAndPassword,
} from "firebase/auth";
const handleSubmit = async (e) => {
e.preventDefault();
if (email && password) {
createUserWithEmailAndPassword(auth, email, password)
.then(() => console.log("Created!"))
.catch((err) => console.log("Error!", err))
}
}
I have a problem with flickering my private routes while using my AuthContext. Below is the code for my Private Route:
import React from 'react';
import { Navigate } from 'react-router-dom';
import { UserAuth } from '../../Context/AuthContext';
const PrivateRoute = ({ children }) => {
const { user } = UserAuth();
if (!user) {
return <Navigate to='/login' />;
}
return children;
};
export default PrivateRoute;
No personal information shows, because the user is initialized to {} in Auth Context. but I can still see the page and navbar. Anyone have a solution?
Also, below is AuthContext.js:
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
} from 'firebase/auth';
import { auth } from '../../firebase';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({});
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
}
const logout = () => {
return signOut(auth)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
//console.log(currentUser);
setUser(currentUser);
});
return () => {
unsubscribe();
};
}, []);
return (
<UserContext.Provider value={{ createUser, user, logout, signIn }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};
So I found a solution that's kind of cheeky. I'm not going to post my solution, but basically, I wrapped the return statement return children in the PrivateRoute function with an if statement for a specific item in the user object. This prevents any return and 'solves' the flicker.
I have a problem, watching a video on youtube that used history.push('/login?redirect=shipping'), how can I modify it using navigate, because if I use the string navigate ('/ login?redirect=shipping') even if logged in it returns to home page and does not go to the shipping page.
I solved the problem in this way :
const checkoutHandler = () => {
if(!userInfo){
navigate('/login')
} else{
navigate('/shipping')
}
}
Your solution is good.
Can I fill in some gaps?
We need to get the state of user to check if user logged in to proceed with if statement.
After this your solution will work.
import React from 'react'
import { useNavigate } from 'react-router-dom'
import { useSelector } from 'react-redux'
//....some imports
const CartScreen = () => {
//.......some code before
const navigate = useNavigate()
const userLogin = useSelector((state) => state.userLogin)
const { userInfo } = userLogin
const checkoutHandler = () => {
if (!userInfo) {
navigate('/login')
} else {
navigate('/shipping')
}
}
//...return JSX...
**Try this one.
const navigate = useNavigate()
const checkoutHandler = () => {
navigate('/signin?redirect=/shipping')
}
Have you sort out this problem or not? If you are facing the same problem then I suggest you go on your login page and make some changing.
Import useHistory and useLocation:
import { Link, useHistory, useLocation } from 'react-router-dom'
Then in functions apply this:
const history = useHistory();
const location = useLocation();
I am completely new to this. Tried for hours to find some sort of explanation on this.
Basically I am trying to replicate a project to understand the main concepts. In the project the code apparently works just fine, I however receive a "TypeError: Cannot read properties of undefined (reading 'search')" error. I hope that someone here can explain to me, what I am missing.
I believe the Errors to come from the following lines of code:
SigninScreen:
const redirect = props.location.search
? props.location.search.split("=")[1]
: "/";
ProductScreen:
const productId = props.match.params.id;
Kind Regards,
Makani
SigninScreen:
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link, } from "react-router-dom";
import { signin } from "../actions/userActions";
import Loadingbox from "../components/LoadingBox";
import MessageBox from "../components/MessageBox";
export default function SigninScreen(props) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const redirect = props.location.search
? props.location.search.split("=")[1]
: "/";
const userSignin = useSelector((state) => state.userSignin);
const { userInfo, loading, error } = userSignin;
const dispatch = useDispatch();
const submitHandler = (e) => {
e.preventDefault();
dispatch(signin(email, password));
};
useEffect(() => {
if (userInfo) {
props.history.push(redirect);
}
}, [props.history, redirect, userInfo]);
return ( ... );
}
ProductScreen:
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom";
import { detailsProduct } from "../actions/productActions";
import LoadingBox from "../components/LoadingBox";
import MessageBox from "../components/MessageBox";
import Rating from "../components/Rating";
export default function ProductScreen(props) {
const productId = props.match.params.id;
const dispatch = useDispatch();
const [qty, setQty] = useState(1);
const productDetails = useSelector((state) => state.productDetails);
const { loading, error, product } = productDetails;
useEffect(() => {
dispatch(detailsProduct(productId));
}, [dispatch, productId]);
const addToCartHandler = () => {
props.history.push(`/cart/${productId}?qty=${qty}`);
};
return ( ... );
}
I had same error.
In v6 you have to use useLocaion() like:
const location = useLocation();
const redirect = location.search
? location.search.split('=')[1]
: '/';
From the documentation for React Router v5:
The router will provide you with a location object in a few places:
· Route component as this.props.location
· Route render as ({ location }) => ()
· Route children as ({ location }) => ()
· withRouter as this.props.location
A component receives the location prop automatically in 4 usages mentioned about. Since you have not described withRouter, I can only assume that your case is Option 1.
React Router adds the location prop to the component automatically if it is directly in the Route component.
// if this is the case, SignIn receives a location prop
<Route path="/path" component={SigninScreen} />
If you want to add location prop manually, you can use withRouter higher order component.
export default withRouter(SignInScreen);
I have a simple Dashboard component that relies on React context to manage auth. It contains a custom hook useAuth to extract the current user as well as the auth related functions: login, logout, etc.
This is the Context file: AuthContext.js:
import React, { createContext, useContext, useState, useEffect } from "react";
import { auth } from "../config/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, password) {
return auth.createUserWithEmailAndPassword(email, password);
}
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password);
}
function logout() {
return auth.signOut();
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
signup,
login,
logout,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}
This is the Dashboard.js component:
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
export default function Dashboard() {
const { currentUser, logout } = useAuth();
const [error, setError] = useState("");
const history = useHistory();
const handleLogout = async () => {
setError("");
try {
await logout();
history.push("/login");
} catch (e) {
setError(e.message);
}
};
return (
<div>
{error && <p>{error}</p>}
<h1>This is the Dashboard</h1>
<h5>Email: {currentUser.email}</h5>
<button onClick={handleLogout} type="button">
Logout
</button>
</div>
);
}
As recommened by React Testing Library I have created a test-utils.js file:
import React, { createContext } from "react";
import { render } from "#testing-library/react";
import { BrowserRouter as Router } from "react-router-dom";
const AuthContext = createContext();
const currentUser = {
email: "abc#abc.com",
};
const signup = jest.fn();
const login = jest.fn();
const logout = jest.fn();
const AllTheProviders = ({ children }) => {
return (
<Router>
<AuthContext.Provider value={{ currentUser, signup, login, logout }}>
{children}
</AuthContext.Provider>
</Router>
);
};
const customRender = (ui, options) => {
render(ui, { wrapper: AllTheProviders, ...options });
};
export * from "#testing-library/react";
export { customRender as render };
However, when running Dashboard.test.js I get error
TypeError: Cannot destructure property 'currentUser' of '((cov_5mwatn2cf(...).s[0]++) , (0 , _AuthContext.useAuth)(...))' as it is undefined.
4 |
5 | export default function Dashboard() {
> 6 | const { currentUser, logout } = useAuth();
| ^
7 | const [error, setError] = useState("");
8 | const history = useHistory();
import React from "react";
import Dashboard from "./Dashboard";
import { act, render, screen } from "../config/test-utils-dva";
beforeEach(async () => {
await act(async () => {
render(<Dashboard />);
});
});
test("displays dashboard", () => {
expect(screen.getByText(/dashboard/i)).toBeInTheDocument();
});
I think it is because Dashboard component is trying to use useAuth from AuthContext.js, how can I force the rendered Dashboard component to use the mocked data that I am sending in the test-utils.jsfile?
Instead of creating a new context, use the AuthContext from context/AuthContext for <AuthContext.Provider>, as that's the context that the hook uses.
So, in AuthContext.js, export the context instance:
export const AuthContext = createContext();
Then, in your test-util.js file, instead of again calling createContext (which will create a completely separate context instance - the contexts are not the same even if they are stored in a variable with the same name!), just import the previously exported instance:
import { AuthContext } from "../context/AuthContext";
const AllTheProviders = ({ children }) => {
return (
<Router>
<AuthContext.Provider value={{ currentUser, signup, login, logout }}>
{children}
</AuthContext.Provider>
</Router>
);
};