I'm trying to figure out the best way to create a global state variable that will hold a firebase authentication user id.
For example the below code would check if a user is logged in and then send them to welcome page if successful.
But I also need to setup up private routes on a different file, I want to be able to share the getId state. I read that useContext can do this but unsure how to implement it. Please advise, thanks
const [getId, setId] = useState("");
const login = async ( id ) => {
return setId(id);
};
firebase.auth().onAuthStateChanged((user) => {
if (user) {
login(user.uid).then(() => {
history.push("/welcome");
});
} else {
history.push("/");
}
});
const PrivateRoute = ({ getId, component: Component, ...rest }) => (
<Route
{...rest}
component={(props) =>
getId ? (
<div>
<Component {...props} />
</div>
) : (
<Redirect to="/" />
)
}
/>
);
I'll give you my example to have an Auth Context. Here are the parts:
The _app.js file:
import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import Footer from '../components/Footer'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '#material-ui/core'
import theme from '../styles/theme'
export default function App({ Component, pageProps }) {
const router = useRouter()
return(
<AnimatePresence exitBeforeEnter>
<CartProvider>
<AuthProvider>
<ThemeProvider theme={theme}>
<Header />
<motion.div key={router.pathname} className="main">
<Component { ...pageProps } />
<Footer />
</motion.div>
</ThemeProvider>
</AuthProvider>
</CartProvider>
</AnimatePresence>
)
}
The item of significance is the <AuthProvider> component. That's where the context is wrapped.
The AuthContent.js file:
import { createContext, useContext, useEffect, useState } from 'react'
import { auth } 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 login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
function signOut() {
return auth.signOut();
}
function signUp(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function getUser() {
return auth.currentUser
}
function isAdmin() {
return auth.currentUser.getIdTokenResult()
.then((idTokenResult) => {
if (!!idTokenResult.claims.admin) {
return true
} else {
return false
}
})
}
function isEditor() {
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
getUser,
login,
signOut,
signUp
}
return (
<AuthContext.Provider value={value}>
{ !loading && children }
</AuthContext.Provider>
)
}
This is where the state is stored and accessed, including all of the helpers (signup, signout, login, etc).
How to use it:
import { Button, Card, CardHeader, CardContent, Link, TextField, Typography } from '#material-ui/core'
import { motion } from 'framer-motion'
import { useRef, useState } from 'react'
import { useAuth } from '../contexts/AuthContext'
import { useRouter } from 'next/router'
export default function SignupForm() {
const router = useRouter()
const { signUp } = useAuth()
const [state, setState] = useState({
email: "",
password: "",
passwordConfirm: ""
})
const [error, setError] = useState("")
function handleForm(e) {
setState({
...state,
[e.target.name]: e.target.value
})
}
async function handleSubmit(e) {
if (state.password !== state.passwordConfim) {
setError("Passwords do not match")
}
await signUp(state.email, state.password)
.catch(err => console.log(JSON.stringify(err)) )
router.push("/account")
}
return(
<motion.div>
<Card >
<CardHeader title="Header" />
<CardContent>
<TextField label="email" name="email" variant="outlined" onChange={ handleForm } />
<TextField label="password" name="password" type="password" variant="outlined" onChange={ handleForm } />
<TextField label="Password Confirmation" name="passwordConfirm" type="password" variant="outlined" onChange={ handleForm } />
{error && <Alert severity="error" variant="filled" >{error}</Alert>}
<Button onClick={ handleSubmit }>
<Typography variant="button">Sign Up</Typography>
</Button>
</CardContent>
</Card>
</motion.div>
)
}
You import { useAuth } from your context (I usually put mine in a context folder) and then you can invoke instances of the variables inside the component by destructuring (e.g. const { currentUser, login } = useAuth())
Related
I am making a site whereby after the user signs in, the user is meant to be redirected to the home page. The homepage and all the other pages of the site are only accessible by signed in users but even after a user signs in(firebase auth), the rest of the site(protected routes) is still not accessible and the only page accessible is the login page. The technologies I am using are react, react router dom and firebase and this is how my code looks like, starting with the App.js
import Home from "./pages/home/Home";
import Login from "./pages/login/Login";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import List from "./pages/list/List";
import User from "./pages/user/User";
import AddNew from "./pages/addnew/AddNew";
import { useContext } from "react";
import { AuthContext } from "./context/AuthContext";
function App() {
const {currentUser} = useContext(AuthContext);
const RequireAuth = ({ children }) => {
return currentUser ? children : <Navigate to="/login" />;
};
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/login" exact element={<Login />} />
<Route path="/" exact element={ <RequireAuth> <Home /> </RequireAuth> } />
<Route path="/users" exact element={<RequireAuth><List /></RequireAuth>} />
<Route path="/users/:id" exact element={<RequireAuth><User /></RequireAuth>} />
<Route path="/add" exact element={<RequireAuth><AddNew /></RequireAuth>} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
And then followed by the login.js page
import React,{useState} from 'react'
import { useContext } from 'react';
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from '../../firebase';
import "./login.css";
import {useNavigate} from "react-router-dom";
import { AuthContext } from '../../context/AuthContext';
export default function Login() {
const [error, seterror] = useState(false);
const [email, setemail] = useState("");
const [password, setpassword] = useState("");
const navigate = useNavigate();
const {dispatch} = useContext(AuthContext)
const handleLogin = (e) => {
e.preventDefault();
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
dispatch({type: "LOGIN", payload: user});
navigate("/");
})
.catch((error) => {
seterror(true);
console.log(error.message);
});
}
return (
<div className='login'>
<form onSubmit={handleLogin}>
<input className='ok' type="email" placeholder='email' onChange={e => setemail(e.target.value)} />
<input className='ok' type="password" placeholder='password' onChange={e => setpassword(e.target.value)} />
<button className='sb'>Submit</button>
{error && <span className='ks'>Wrong email or password</span>}
</form>
</div>
)
}
And then I have the authreducer.js file that deals with the state
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN": {
return {
currentUser: action.payload,
}
}
case "LOGOUT": {
return {
currentUser: null
}
}
default:
return state;
}
}
export default AuthReducer
And finally the authcontext.js file
import { createContext, useEffect, useReducer } from "react";
import AuthReducer from "./AuthReducer";
const INITIAL_STATE = {
currentUser: JSON.parse(localStorage.getItem("user")) || null,
}
export const AuthContext = createContext(INITIAL_STATE);
export const AuthContextProvider = ({children}) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.currentUser))
}, [state.currentUser])
return (
<AuthContext.Provider value={{current: state.current, dispatch}}>
{children}
</AuthContext.Provider>
)
}
I do not know what could be causing this problem but I have an idea that it has something to do with the state because it was redirecting well before I started combining it with the state. What could be the problem
Issue
From that I can see, the App isn't destructuring the correct context value to handle the conditional route protection.
The AuthContextProvider provides a context value with current and dispatch properties
<AuthContext.Provider value={{ current: state.current, dispatch }}>
{children}
</AuthContext.Provider>
but App is accessing a currentUser property, which is going to be undefined because state.current is undefined.
const { currentUser } = useContext(AuthContext);
const RequireAuth = ({ children }) => {
return currentUser ? children : <Navigate to="/login" />;
};
The Navigate component will always be rendered.
Solution
Assuming the handleLogin handler correctly updates the state then the solution is to be consistent with state properties.
<AuthContext.Provider value={{ currentUser: state.currentUser, dispatch }}>
{children}
</AuthContext.Provider>
...
const { currentUser } = useContext(AuthContext);
const RequireAuth = ({ children }) => {
return currentUser ? children : <Navigate to="/login" />;
};
I am trying to do Firebase auth in a NextJS project but I am getting the following error:
TypeError: Cannot destructure property 'currentUser' of '(0 , _lib_contexts_AuthContext__WEBPACK_IMPORTED_MODULE_6__.useAuth)(...)' as it is undefined.
9 | export default function Login() {
10 | // const router = useRouter();
> 11 | const { currentUser, setCurrentUser, login, logout } = useAuth();`
This is my AuthContext.js file:
import React, { createContext, useContext, useEffect, useState } from 'react';
import { signOut, signInWithEmailAndPassword, onAuthStateChanged } from 'firebase/compat/auth';
import { auth } from '../../core/firebase';
export const AuthContext = createContext();
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, user => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe();
}, []);
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
async function logout() {
setCurrentUser(null);
await signOut(auth);
console.log('logout');
return;
}
function resetPassword(email) {
return auth.sendPasswordResetEmail(email);
}
const value = {
currentUser,
setCurrentUser,
login,
logout,
resetPassword,
};
return <AuthContext.Provider value={value}>{!loading && children}</AuthContext.Provider>;
}
export function useAuth() {
return useContext(AuthContext);
}
This is my login.jsx page, somehow it is not getting any of the exported values from the AuthContext file. Every example I have seen uses this destructuring format for getting the values form useAuth() so I am not sure why I am getting this error now
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import { useEffect, useState } from 'react';
import { useAuth } from '../lib/contexts/AuthContext';
export default function Login() {
const { currentUser, setCurrentUser, login, logout } = useAuth();
async function handleLogin(email, password) {
try {
await login(email, password);
} catch (err) {
console.log(err);
}
}
function handleLogout() {
logout();
}
return (
<>
<div>Currently logged in: {currentUser?.email}</div>
<Formik
initialValues={{
email: '',
password: '',
}}
validationSchema={Yup.object({
email: Yup.string().email('Invalid email address').required('Required'),
password: Yup.string().required('No password provided.'),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
setSubmitting(false);
}, 400);
handleLogin(values.email, values.password);
}}>
<Form>
<label className='border border-black' htmlFor='Email'>
Email
</label>
<Field className='border border-black' id='email' name='email' />
<ErrorMessage component='a' className='border border-black' name='email' />
<label className='border border-black' htmlFor='Email'>
Password
</label>
<Field className='border border-black' id='password' name='password' />
<ErrorMessage component='a' className='border border-black' name='password' />
<div className='mt-8'>
<button
disabled={currentUser ? true : false}
type='submit'
className='border border-black'>
Login
</button>
</div>
</Form>
</Formik>
<div>
<button
disabled={currentUser ? false : true}
className='border border-black'
onClick={handleLogout}>
Logout
</button>
</div>
</>
);
}
I am wrapping the _app.js return in the AuthProvider which seems to be the most common cause for this error.
import '../styles/globals.css';
import { AuthProvider } from '../lib/contexts/AuthContext';
function MyApp({ Component, pageProps }) {
return (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
);
}
export default MyApp;
I'm creating a ProtectedRoute component in React that will take a user state variable as prop.
This user is from my checkUser() func using amplify's Auth.currentAuthenticatedUser().
function App() {
const [user, setUser] = useState();
const { Auth, Hub } = useContext(AmplifyContext)
async function checkUser() {
try {
const loggedInUser = await Auth.currentAuthenticatedUser();
setUser(loggedInUser);
console.log(loggedInUser);
// get null first time?
} catch(e) {
setUser(null);
console.log(e.message);
}
}
useEffect(() => {
checkUser();
}, [Auth])
return (
<Router>
<Suspense fallback={<p>...loading...</p>}>
<Switch>
<IsUserLoggedIn user={user} loggedInPath={ROUTES.DASHBOARD} path={ROUTES.LOGIN}>
<Route path={ROUTES.LOGIN} component={Login} />
</IsUserLoggedIn>
<IsUserLoggedIn user={user} loggedInPath={ROUTES.DASHBOARD} path={ROUTES.SIGN_UP}>
<Route path={ROUTES.SIGN_UP} component={SignUp} />
</IsUserLoggedIn>
<ProtectedRoute user={user} path={ROUTES.DASHBOARD} exact>
<Route path={ROUTES.DASHBOARD} exact component={Dashboard} />
</ProtectedRoute>
<Route path={ROUTES.RESET_PW} component={ResetPw} />
<Route component={NoPage} />
</Switch>
</Suspense>
</Router>
);
}
// Protected Route Component
import { Route, Redirect } from "react-router-dom";
import * as ROUTES from '../constants/routes';
export default function ProtectedRoute({user, children, ...restProps}) {
console.log(user);
return (
<Route
{...restProps}
render={({location}) => {
if(user) {
return children;
}
if(!user) {
return (
<Redirect
to={{
pathname: ROUTES.LOGIN,
state: { from: location }
}}
/>
)
}
return null;
}}
/>
)
}
// login component
import { useState, useContext } from "react";
import { Link } from "react-router-dom";
import { useHistory } from 'react-router';
import AmplifyContext from "../context/amplify";
import * as ROUTES from '../constants/routes';
export default function Login() {
const { Auth, Hub } = useContext(AmplifyContext);
const history = useHistory();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const invalid = !username || !password;
const handleLogin = async (e) => {
e.preventDefault();
try {
// amplify Auth login
await Auth.signIn(username, password);
history.push(ROUTES.DASHBOARD);
console.log('logged in');
} catch(e) {
setError(e.message);
setPassword('');
}
}
return (
<div className="auth-container">
<h2 className="auth-title">Log In</h2>
<div className="login-form-container">
<form className="form login-form" onSubmit={handleLogin}>
<input autoFocus type="text" placeholder="username" aria-label="username" value={username} onChange={({target}) => setUsername(target.value)} />
<input type="password" placeholder="password" aria-label="password" value={password} onChange={({target}) => setPassword(target.value)} />
{error && (<p style={{color: 'red'}}>{error}</p>)}
<div className="form-action-container">
<div className="button-container">
<button disabled={invalid} className="form-button" type='submit'>Log In</button>
<p>Need an Account? <span><Link to={ROUTES.SIGN_UP}>Sign Up</Link></span></p>
</div>
<p>Forget your password? <span><Link to={ROUTES.RESET_PW}>Reset</Link></span></p>
</div>
</form>
</div>
</div>
)
}
The current problem is that the useEffect (or maybe the Auth method?) isn't updating the state and so the first time I click "login" in my login component, it returns 'null' from my protectedRoute component's as well as the main App component's console.log(user), returning null. Only after I refresh, does it change and let me get the user log as well as directed into the protectedRoute.
This is also true for my logOut scenario.
export default function Dashboard() {
const { Auth, Hub } = useContext(AmplifyContext);
const history = useHistory();
const handleLogOut = async (e) => {
e.preventDefault();
// amplify call to sign out
await Auth.signOut();
history.push(ROUTES.LOGIN);
}
return (
<div className="dashboard-container">
<h1>Welcome </h1>
<button onClick={handleLogOut}>log out</button>
</div>
)
}
I don't get logged out, nor do I get redirected unless I reload the page.
Why aren't the Auth.signOut() and the Auth.currentAuthenticatedUser() methods run like I want it to?
made her work a bit better after putting all auth related state into context provider and wrapping it around all {children} components and then using Hub to listen for changes to log out. (I had to stop using my route helper functions, so that's kind of a bummer. But it works at the moment. I will keep it as the solution). I had to use the amplify guideline for custom auth, so still not so satisfied...
Each related state variables that needs to be used in each component is using it from context (left it out for length).
// storing all state related to Authorization
import { createContext, useContext, useState } from "react";
import AmplifyContext from "./amplify";
const AuthContext = createContext();
function AuthContextProvider({ children }) {
const [formType, setFormType] = useState("signUp");
const [fullName, setFullName] = useState("");
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [authCode, setAuthCode] = useState("");
const [error, setError] = useState("");
const [user, setUser] = useState(null);
const { Auth } = useContext(AmplifyContext);
let invalid;
const checkUser = async () => {
try {
const loggedInUser = await Auth.currentAuthenticatedUser();
setUser(loggedInUser);
console.log(user);
if (user) {
setFormType("dashboard");
} else {
setUser(null);
setFormType("login");
}
} catch (e) {
console.log(e.message);
}
};
const handleSignUp = async (e) => {
e.preventDefault();
try {
// amp auth signup. attribute must match (ie: if email is needed, state var needs to be called email (not other name))
await Auth.signUp({ username, password, attributes: { email } });
console.log("signed up");
setFullName("");
setUsername("");
setEmail("");
setPassword("");
setFormType("confirmSignUp");
} catch (e) {
console.log(e.message);
setError(e.message);
}
};
const handleConfirmAuthCode = async (e) => {
e.preventDefault();
try {
// amp auth confirm sign up
await Auth.confirmSignUp(username, authCode);
setFormType("login");
} catch (e) {
console.log(e.message);
setError(e.message);
}
};
const handleLogin = async (e) => {
e.preventDefault();
try {
// amplify Auth login
await Auth.signIn(username, password);
setUsername("");
setPassword("");
console.log("logged in");
setFormType("dashboard");
} catch (e) {
setError(e.message);
setPassword("");
}
};
const handleLogOut = async (e) => {
e.preventDefault();
// amplify call to sign out
await Auth.signOut();
//set some loading or redirect?
};
return (
<AuthContext.Provider
value={{
error,
setError,
handleSignUp,
checkUser,
handleConfirmAuthCode,
handleLogin,
handleLogOut,
fullName,
setFullName,
username,
setUsername,
email,
setEmail,
password,
setPassword,
formType,
setFormType,
authCode,
setAuthCode,
invalid,
user,
setUser,
}}
>
{children}
</AuthContext.Provider>
);
}
export { AuthContextProvider, AuthContext };
// top
ReactDOM.render(
<AmplifyContext.Provider value={{ Auth, Hub }}>
<AuthContextProvider>
<App />
</AuthContextProvider>
</AmplifyContext.Provider>,
document.getElementById("root")
);
// inside the App component (not yet finished)
import { useContext, useEffect } from "react";
import AmplifyContext from "./context/amplify";
import { AuthContext } from "./context/AuthContext";
import ConfirmSignUp from "./pages/confirmSignUp";
import Login from "./pages/login";
import SignUp from "./pages/sign-up";
import Dashboard from "./pages/dashboard";
import ResetPass from "./pages/reset-pw";
function App() {
const { Hub } = useContext(AmplifyContext);
const {
formType,
setFormType,
username,
setUsername,
error,
setError,
checkUser,
handleLogOut,
} = useContext(AuthContext);
async function setAuthListener() {
Hub.listen("auth", (data) => {
switch (data.payload.event) {
case "signIn":
console.log(`${username} signed in`);
break;
case "signOut":
console.log("user signed out");
setFormType("login");
break;
default:
break;
}
});
}
useEffect(() => {
checkUser();
setAuthListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
{formType === "signUp" && <SignUp />}
{formType === "confirmSignUp" && <ConfirmSignUp />}
{formType === "login" && <Login />}
{formType === "dashboard" && (
<Dashboard handleLogOut={handleLogOut} />
)}
{formType === "reset" && (
<ResetPass />
)}
</>
);
}
export default App;
This might look like a similar question but I'm unable to get the answer of it on Stackoverflow.
I have two components - A.js and B.js
A.js
(The below code is inside return() )
<Link to={{
pathname: `${simulationId}/edit/${ruleName}`,
search: createButtonQuery,
previewFlag = true,
}}>
<IconButton color="primary" size="small">
<PageviewOutlinedIcon/>
</IconButton>
</Link>
B.js
(The below code is inside return() )
<DialogTitle>{name ? 'Print true' : 'Print false'}</DialogTitle>
Issue:
I want to test for previewFlag inside the B.js. I want to check when the previewFlag is true and name exists, 'Print true' should be returned by the <DialogTitle>
I don't know how to use previewFlag inside B.js though.
Kindly note that <PageViewOutlineIcon> is part of material-ui here.
The both components aren't being imported by each other.
To use a context, there are a few things you have to do:
1: Create a context file (I usually put mine in a contexts folder)
import { createContext, useContext, useEffect, useState } from 'react'
import { auth } 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 login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
function signOut() {
return auth.signOut();
}
function signUp(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function getUser() {
return auth.currentUser
}
function isAdmin() {
return auth.currentUser.getIdTokenResult()
.then((idTokenResult) => {
if (!!idTokenResult.claims.admin) {
return true
} else {
return false
}
})
}
function isEditor() {
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
getUser,
login,
signOut,
signUp
}
return (
<AuthContext.Provider value={value}>
{ !loading && children }
</AuthContext.Provider>
)
}
This is the file that can hold whatever you want to check for
Then, you need to put it in your App/_app (depending on how you're using react) as in this case:
import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import Footer from '../components/Footer'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '#material-ui/core'
import theme from '../styles/theme'
export default function App({ Component, pageProps }) {
const router = useRouter()
return(
<AnimatePresence exitBeforeEnter>
<CartProvider>
<AuthProvider>
<ThemeProvider theme={theme}>
<Header />
<motion.div key={router.pathname} className="main">
<Component { ...pageProps } />
<Footer />
</motion.div>
</ThemeProvider>
</AuthProvider>
</CartProvider>
</AnimatePresence>
)
}
Then, in any component that you want access to those contexts, you can invoke it to use the values (or helper functions):
import { Alert, Grid, TextField, Button, makeStyles, Typography } from '#material-ui/core'
import { useAuth } from '../contexts/AuthContext'
import { useState } from 'react'
const theme = makeStyles({
form: {
width: '100vw',
maxWidth: '400px',
margin: 'auto',
padding: '1rem',
'& > div': {
paddingBottom: '1rem'
}
}
})
export default function LoginForm() {
const { login } = useAuth();
const styles = theme()
const [state, setState] = useState({
email: "",
password: ""
})
const [error, setError] = useState()
const { googleLogin } = useAuth()
function handleForm(e) {
setState({
...state,
[e.target.name]: e.target.value
})
}
async function handleLogin() {
await login(state.email, state.password)
.catch(err => {
console.log(err)
setError(JSON.stringify(err))
})
}
return(
<Grid container className={styles.form} direction="column" alignContent="stretch" justify="center">
<Grid item>
<Typography variant="h3">Login</Typography>
</Grid>
<Grid item>
{error && <Alert severity="error" variant="filled" >{error}</Alert>}
<TextField fullWidth name="email" label="Email" variant="outlined" onChange={handleForm}/>
</Grid>
<Grid item>
<TextField fullWidth name="password" type="password" label="Password" variant="outlined" onChange={handleForm} />
</Grid>
<Grid item>
<Button variant="contained" color="primary"fullWidth onClick={handleLogin}>Log In</Button>
</Grid>
</Grid>
)
}
notice how I import the useAuth instance (which is just a context I've named that. You can name it whatever) in my loginForm component, and then I can destructure out the values that I've exposed: const { login } = useAuth() Something like this: const { value, helperFunction } = useAuth() - assuming you're exporting a value and a function helperFunction from your context
I am creating sign-up and sign-in on a website but I am getting this error TypeError: Cannot read property 'pathname' of undefined I have no idea where this error comes from. I am new to redux So if anyone knows why I get this error please explain to me, so the next time I will know if I still get this error.
Thank you
Here is my code
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import store from './Store';
window.store = store;
ReactDOM.render(
<Provider store= {store}>
<Router>
<React.StrictMode>
<App />
</React.StrictMode>
</Router>
</Provider>,
document.getElementById('root')
);
reportWebVitals();
app.js
import './App.css';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import HomePage from './Home_Search_Client/Containers/HomePage';
import About from './Home_Search_Client/Containers/About';
import Feedback from './Home_Search_Client/Containers/Feedback';
import Signup from './Home_Search_Client/Containers/Signup';
import Signin from './Home_Search_Client/Containers/Signin';
import PrivateRoute from './Home_Search_Client/Components/HOC/PrivateRoute';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';
import { isUserLoggedIn } from './Home_Search_Client/actions';
function App() {
const dispatch = useDispatch();
const auth = useSelector((state) => state.auth);
useEffect(() => {
if(!auth.authenticate){
dispatch(isUserLoggedIn)
}
}, [auth.authenticate]);
return (
<Router>
<Switch>
<PrivateRoute path='/' exact component={HomePage} />
<PrivateRoute path='/about' component={About} />
<PrivateRoute path='/feedback' component={Feedback} />
<Route path='/signup' component={Signup} />
<Route path='/signin' component={Signin} />
</Switch>
</Router>
);
}
export default App;
Store (index.js)
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../Home_Search_Client/reducers';
import thunk from 'redux-thunk';
const store = createStore(rootReducer, composeWithDevTools(
applyMiddleware(thunk)
));
export default store;
auth.actinons.js
import axios from '../helpers/axios';
import { authConstants } from './constants';
export const signin = (user) => {
return async (dispatch) => {
dispatch({ type: authConstants.LOGIN_REQUEST });
const res = await axios.post(`/signin`, {
...user
});
if (res.status === 200) {
const { token, user } = res.data;
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));
dispatch({
type: authConstants.LOGIN_SUCCESS,
payload: {
token, user
}
});
} else {
if (res.status === 400) {
dispatch({
type: authConstants.LOGIN_FAILURE,
payload: { error: res.data.error }
});
}
}
}
}
export const isUserLoggedIn = () => {
return async dispatch => {
const token = localStorage.getItem('token');
if (token) {
const user = JSON.parse(localStorage.getItem('user'));
dispatch({
type: authConstants.LOGIN_SUCCESS,
payload: {
token, user
}
});
}else{
dispatch({
type: authConstants.LOGIN_FAILURE,
payload: { error: 'Failed to login' }
});
}
}
}
export const signout = () => {
return async dispatch => {
dispatch({ type: authConstants.LOGOUT_REQUEST });
const res = await axios.post(`/signout`);
if (res.status === 200) {
localStorage.clear();
dispatch({ type: authConstants.LOGOUT_SUCCESS });
} else {
dispatch({
type: authConstants.LOGOUT_FAILURE,
payload: { error: res.data.error }
});
}
}
}
user.action.js
import axios from '../helpers/axios';
import { userContants } from './constants';
export const signup = (user) => {
return async (dispatch) => {
dispatch({ type: userContants.USER_REGISTER_REQUEST });
const res = await axios.post(`/signup`, {
...user
});
if (res.status === 201) {
const { message } = res.data;
dispatch({
type: userContants.USER_REGISTER_SUCCESS,
payload: { message }
});
} else {
if (res.status === 400) {
dispatch({
type: userContants.USER_REGISTER_FAILURE,
payload: { error: res.data.error }
});
}
}
}
};
Signup.js(Containers)
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Button, Col, Container, Form, Row } from 'react-bootstrap'
import Layout from '../../Components/Layout'
import Input from '../../Components/UI/Input'
import { signup } from '../../actions'
import { Redirect } from 'react-router'
const Signup = (props) => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const auth = useSelector((state) => state.auth)
const user = useSelector((state) => state.user)
const dispatch = useDispatch();
useEffect(() => {
if(!user.loading){
setFirstName('');
setLastName('');
setEmail('');
setPassword('');
}
}, [user.loading]);
const userSignup = (e) => {
e.preventDefault();
const user = {
firstName,
lastName,
email,
password
};
dispatch(signup(user));
};
if(auth.authenticate) {
return <Redirect to={'/'} />
}
if(user.loading) {
return <p>Loading...</p>
}
return (
<Layout>
<Container>
{user.message}
<Row>
<Col md={{ span:6, offset:3 }}>
<Form onSubmit={userSignup}>
<Row>
<Col md = {6}>
<Input
label = 'First Name'
placeholder='First Name'
value= {firstName}
type='text'
onChange={(e) => setFirstName(e.target.value)}
/>
</Col>
<Col md = {6}>
<Input
label = 'Last Name'
placeholder='Last Name'
value= {lastName}
type='text'
onChange={(e) => setLastName(e.target.value)}
/>
</Col>
</Row>
<Input
label='Email'
placeholder='Email'
value={email}
type='email'
onChange={(e) => setEmail(e.target.value)}
/>
<Input
label='Password'
placeholder='Password'
value={password}
type='password'
onChange={(e) => setPassword(e.target.value)}
/>
<Button variant='primary' type='submit' >
Submit
</Button>
</Form>
</Col>
</Row>
</Container>
</Layout>
)
}
export default Signup
Signin.js(Containers)
import React, { useState } from 'react'
import { Button, Col, Container, Form, Row } from 'react-bootstrap'
import Layout from '../../Components/Layout';
import Input from '../../Components/UI/Input';
import { useDispatch, useSelector } from 'react-redux';
import { signin } from '../../actions'
import { Redirect } from 'react-router';
const Signin = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const auth = useSelector(state => state.auth);
const dispatch = useDispatch();
const userLogin = (e) => {
e.preventDefault();
const user = {
email, password
}
dispatch(signin(user))
}
if(auth.authenticate) {
return <Redirect to={'/'} />
}
return (
<Layout>
<Container>
<Row>
<Col md={{ span: 6, offset: 3 }}>
<Form onSubmit={userLogin}>
<Input
label='Email'
placeholder='Email'
value={email}
type='email'
onChange={(e) => setEmail(e.target.value)}
/>
<Input
label='Password'
placeholder='Password'
value={password}
type='password'
onChange={(e) => setPassword(e.target.value)}
/>
<Button variant='primary' type='submit'>
Submit
</Button>
</Form>
</Col>
</Row>
</Container>
</Layout>
)
}
export default Sign in
axios.js (helpers)
import axios from 'axios';
import { api } from '../../urlConfig';
import store from '../../Store';
import { authConstants } from '../actions/constants';
const token = window.localStorage.getItem('token');
const axiosInstance = axios.create({
baseURL: api,
headers:{
'Authorization': token ? `${token}` : ''
}
});
axiosInstance.interceptors.request.use((req) => {
const { auth } = store.getState();
if(auth.token){
req.headers.Authorization = `${auth.token}`
}
return req;
});
axiosInstance.interceptors.response.use((res) => {
return res;
}, (error) => {
console.log(error.response);
const status = error.response ? error.response.status : 500;
if(status && status === 500) {
localStorage.clear();
store.dispatch({ type: authConstants.LOGOUT_SUCCESS });
}
return Promise.reject(error);
})
export default axiosInstance;
PrivateRoute.js
import React from 'react'
import { Redirect, Route } from 'react-router'
const PrivateRoute = ({ component: Component, ...rest }) => {
return <Route {...rest} component={(props) => {
const token = window.localStorage.getItem('token');
if(token) {
return <Component {...props} />
}else{
return <Redirect to={'signin'} />
}
}} />
}
export default PrivateRoute;
Header.js
import React from 'react';
import Logo from '../../../Logos/main-logo.png';
import { Navbar, Button, Nav } from 'react-bootstrap';
import { NavLink } from 'react-router-dom';
import KeyboardReturnOutlinedIcon from '#material-ui/icons/KeyboardReturnOutlined';
import './style.css';
import { useDispatch, useSelector } from 'react-redux';
import { signout } from '../../actions';
const Header = () => {
const auth = useSelector((state) => state.auth);
const dispatch = useDispatch();
const logout = () => {
dispatch(signout());
}
const renderLoggedInLinks = () => {
return (
<Nav className='host_btn ml-auto'>
<Nav.Link><NavLink className='NavLink' to={`/`}>Home</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/about`}>About</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/feedback`}>Feedback</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' onClick={logout}>Signout</NavLink></Nav.Link>
<Button className='ml-5'>Become a host <KeyboardReturnOutlinedIcon /> </Button>
</Nav>
);
};
const renderNonLoggedInLinks = () => {
return (
<Nav className='host_btn ml-auto'>
<Nav.Link><NavLink className='NavLink' to={`/`}>Home</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/about`}>About</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/feedback`}>Feedback</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/signup`}>Signup</NavLink></Nav.Link>
<Nav.Link><NavLink className='NavLink ml-2' to={`/signin`}>Signin</NavLink></Nav.Link>
<Button className='ml-5'>Become a host <KeyboardReturnOutlinedIcon /> </Button>
</Nav>
)
}
return (
<Navbar className='mt-2 ml-2 mr-2' collapseOnSelect expand='sm' bg='light' variant='light'>
<Navbar.Toggle aria-controls='responsive-navbar-nav' />
<Navbar.Collapse id='responsive-navbar-nav' >
<Navbar.Brand className='logo-img' to="#home"><img src={Logo} /></Navbar.Brand>
<Nav className='host_btn ml-auto'>
{auth.authenticate ? renderLoggedInLinks() : renderNonLoggedInLinks()}
</Nav>
</Navbar.Collapse>
</Navbar>
)
}
export default Header