I have problem with react-routerand useEffect. When I want to login user, useEffecshould take userData from cookie, and ˙navigate` user to different component. But it stuck.
const login = (e) => {
e.preventDefault();
Axios.post("http://localhost:3001/login", {
username: username,
password: password,
}).then((response) => {
setUserData(response.data);
});
};
useEffect(() => {
if (userData) {
navigate("/");
}
}, [userData]);
This is error in console.
I hope you can help me, if you need anything else just let me know.
This is my code from all components.
<Login />
import React, { useState, useEffect } from "react";
import Axios from "axios";
import { useLocation, useNavigate } from "react-router-dom";
import classes from "./Login.module.css";
const Login = ({ setUserData, userData }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const navigate = useNavigate();
const location = useLocation();
Axios.defaults.withCredentials = true;
const login = (e) => {
e.preventDefault();
Axios.post("http://localhost:3001/login", {
username: username,
password: password,
}).then((response) => {
setUserData(response.data);
window.location.reload(true);
});
};
useEffect(() => {
if (userData) {
navigate("/");
}
}, [userData]);
return (
<div className={classes.container}>
<div className={classes.slika}>
<img src="./utilities/scientisc.svg" />
</div>
<div className={classes.login_content}>
<form onSubmit={login}>
<div className={classes.formBorder}>
<img className={classes.avatar} src="utilities/test.png" />
<h2 className={classes.title}>Welcome</h2>
<div /* className={`${classes.input_div} ${classes.one}`}
*/>
<div className={`${classes.inputDiv} ${classes.one}`}>
<h5 className={classes.label}>Korisničko ime</h5>
<input
onChange={(e) => {
setUsername(e.target.value);
}}
type="text"
className={classes.input}
/>
</div>
</div>
<div /* className={`${classes.input_div} ${classes.pass}`}
*/>
<div /* className="div" */>
<h5 className={classes.label}>Lozinka</h5>
<input
onChange={(e) => {
setPassword(e.target.value);
}}
type="password"
className={classes.input}
/>
</div>
</div>
<button type="submit" className={classes.button}>
Prijavi se
</button>
</div>
</form>
<h3 className={classes.labelUpozorenja}>{userData?.message}
</h3>
</div>
</div>
);
};
export default Login;
<App /> component
import "./App.css";
import { BrowserRouter as Router, Routes, Route } from "react-
router-
dom";
import Login from "./pages/Login";
import PrivateRoutes from "./utils/PrivateRoutes";
import { useState, useEffect } from "react";
import Axios from "axios";
import SideMenu from "./components/SideMenu/SideMenu";
function App() {
const [userData, setUserData] = useState();
const [checking, setChecking] = useState(true);
useEffect(() => {
Axios.get("http://localhost:3001/login")
.then((response) => {
if (response.data.loggedIn == true) {
setUserData(response.data);
}
return;
})
.catch((error) => {
console.log(error);
})
.finally(() => {
setChecking(false);
});
}, []);
const handleClick = async () => {
try {
await Axios.post("http://localhost:3001/logout", {
name: "userId",
});
window.location.reload(true);
} catch (error) {
console.error(error);
}
};
return (
<div className="App">
<Router>
<Routes>
<Route
element={<PrivateRoutes userData={userData} checking=
{checking} />}
>
<Route element={<SideMenu handleClick={handleClick}
userData={userData} />} path="/" exact />
{/* <Route element={<Products />} path="/products" />
*/}
</Route>
<Route
element={<Login setUserData={setUserData} userData=
{userData} />}
path="/login"
/>
</Routes>
</Router>
</div>
);
}
export default App;
<PrivateRoutes />
import { Outlet, Navigate } from "react-router-dom";
import React from "react";
const PrivateRoutes = ({ userData, checking }) => {
return checking ? (
<p>Checking...</p>
) : userData?.loggedIn ? (
<Outlet />
) : (
<Navigate to="/login" />
);
};
export default PrivateRoutes;
You are using wrong approach for this, why arent you using navigate() in login ?
const login = (e) => {
e.preventDefault();
Axios.post("http://localhost:3001/login", {
username: username,
password: password,
}).then((response) => {
navigate('/')
});
};
This will redirect the user after successful login.
UPDATE -
Noted that you said in refreshing case it would fail.
Why are you using that e in login? Login doesn't really need an Event I guess !
May be that (e) is preventing the app from refreshing the state! can you try without it ?
Only redirect if the path is /login. This will not cause an infinite loop because it will not redirect unless the path is /login
import { useLocation, useNavigate } from "react-router-dom";
const location = useLocation();
useEffect(() => {
if (userData && location.pathname === "/login") {
navigate("/");
}
}, [userData, location.pathname]);
Note: your login in path might be different, if so just swap out "/login" to the path you're using.
Related
I am making a user context that has all of my user information that I need initially it is null and I update it in the login page but after I login and try to access the context from any other page it is still null
here is the code
CurrentUserContext.js
import { createContext } from "react";
export const CurrentUserContext = createContext(null);
UserContextLayout.jsx
import { Outlet } from "react-router-dom";
import { useState, useMemo, useCallback } from "react";
import { CurrentUserContext } from "../hooks/CurrentUserContext";
const UserContextLayout = () => {
const [currentUserContext, setCurrentUserContext] = useState(null);
return (
<CurrentUserContext.Provider
value={[currentUserContext, setCurrentUserContext]}
>
<Outlet />
</CurrentUserContext.Provider>
);
};
export default UserContextLayout;
MainRouter.jsx
import { Route, Routes } from "react-router-dom";
import Signin from "./pages/Signin";
import LandingPage from "./pages/LandingPage";
import { isAuthenticated } from "./apis/auth/auth-helper";
import UserContextLayout from "./utils/UserContextLayout";
const MainRouter = () => {
return (
<Routes>
<Route element={<UserContextLayout />}>
<Route path="/" element={<LandingPage />}></Route>
<Route
path="/signin"
element={
!isAuthenticated() ? <Signin /> : <Navigate to="/" replace={true} />
}
></Route>
</Route>
</Routes>
);
};
export default MainRouter;
Signin.jsx
import { useState, useContext } from "react";
import { GrTechnology } from "react-icons/gr";
import { CurrentUserContext } from "../hooks/CurrentUserContext";
import { signin } from "../apis/auth/auth-api";
import { authenticate } from "../apis/auth/auth-helper";
import { useNavigate } from "react-router-dom";
const Signin = () => {
const [currentUserContext, setCurrentUserContext] =
useContext(CurrentUserContext);
const navigate = useNavigate();
const [user1, setUser1] = useState({
email: "",
password: "",
});
const [rememberMe, setrememberMe] = useState(false);
const handleChange = (e) => {
setUser1((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
const handleRememberMeClick = () => {
setrememberMe((prev) => !prev);
};
const handleSubmit = async () => {
try {
const response = await signin(user1);
// if (res.status == 200) {
setCurrentUserContext(response.data.user);
authenticate(response.data.token, rememberMe);
navigate("/");
// } else {
// throw new Error("Authentication failed");
// }
} catch (error) {
console.log(error);
}
};
return (
<>
<div >
<div >
<h2 >
Sign in to your account
</h2>
<div >
<div>
<div>
<label htmlFor="email-address">
Email address
</label>
<input
id="email-address"
name="email"
type="email"
autoComplete="email"
required
onChange={handleChange}
/>
</div>
<div>
<label htmlFor="password">
Password
</label>
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
onChange={handleChange}
/>
</div>
</div>
<div >
<div >
<input
id="remember-me"
name="remember-me"
type="checkbox"
onClick={handleRememberMeClick}
/>
<label
htmlFor="remember-me"
>
Remember me
</label>
</div>
</div>
<div>
<button
type="submit"
onClick={handleSubmit}
>
Sign in
</button>
</div>
</div>
</div>
</div>
</>
);
};
export default Signin;
response.data.user has the user data that I want to save in the context but the context is not updated when I call setCurrentUserContext
I have a strange problem where you can navigate to the login page and login, but if you try to enter the address manually into the browser it will just reload the login page every time you hit send. Whats even more strange is that it will show you parts of the navigation that are only visible to logged in users after the first login attempt but it will redirect to the login page still. And if i try to go to other pages that are only for logged in users it won't show them.
If i hit refresh in the browser the login page will instantly start to work again and i can login.
I'm using React Router on a docker container locally.
Is there a way i can fix this?
This is what my React side of things looks like:
App.js
import React, { useContext } from "react";
import {
BrowserRouter as Router,
Routes,
Route,
Navigate
} from "react-router-dom";
import { AuthContext, AuthContextProvider } from './contexts/AuthContext'
import { FacilityDetail } from './components/FacilityDetail'
import { Settings } from './components/Settings'
import { Login } from './components/Login'
import { Reset } from './components/Reset'
import { Navbar } from "./components/Navbar";
import { FacilityUpdate } from "./components/FacilityUpdate";
import { Signup } from "./components/Signup"
import { ConfirmEmail } from "./components/ConfirmEmail";
import { FacilityList } from './components/FacilityList'
import { ResetConfirm } from './components/ResetConfirm'
import { Home } from "./components/Home";
const EnforceAuthOnRoute = ({ children }) => {
const { shouldGoToLogin, user } = useContext(AuthContext)
return user && !shouldGoToLogin ? children : <Navigate replace to="/login" />
}
export default function App() {
return (
<Router>
<AuthContextProvider>
<div>
<Navbar />
{/* A <Routes> looks through its children <Route>s and
renders the first one that matches the current URL. */}
<div className="max-w-8xl mx-auto px-4 sm:px-6 md:px-8">
<Routes>
<Route path="/about" element={<About/>} />
<Route path="/users" element={<Users />} />
<Route path="/facilities/:id" element={<EnforceAuthOnRoute><FacilityDetail /></EnforceAuthOnRoute>} exact />
<Route path="/facilities/:id/update" element={<EnforceAuthOnRoute><FacilityUpdate /></EnforceAuthOnRoute>} exact />
<Route path="/settings" element={<EnforceAuthOnRoute><Settings /></EnforceAuthOnRoute>} exact />
<Route path="/login" element={<Login />} exact />
<Route path="/signup" element={<Signup />} exact />
<Route path="/reset" element={<Reset />} exact />
<Route path="/password-reset/confirm/:uid/:token" element={<ResetConfirm />} exact />
<Route path="/accounts/confirm-email/:key" element={<ConfirmEmail />} exact />
<Route path="/facilities" element={<EnforceAuthOnRoute><FacilityList /></EnforceAuthOnRoute>} exact />
<Route path="/" element={<Home />} exact />
</Routes>
</div>
</div>
</AuthContextProvider>
</Router>
);
}
function About() {
return <h2>About</h2>;
}
function Users() {
return <h2>Users</h2>;
}
Login.js
import { useContext, useState } from 'react';
import { Formik, Field, Form } from 'formik';
import { useNavigate } from "react-router-dom"
import { AuthContext } from '../contexts/AuthContext'
export function Login() {
const [loading, setLoading] = useState(false)
const { login } = useContext(AuthContext)
const navigate = useNavigate()
function handleSubmit(values) {
setLoading(true)
login(values).then(() => {
navigate('/facilities')
}).finally(() => setLoading(false))
}
return (
<div>
{loading && "Loading..."}
<Formik
initialValues={{
email: '',
password: '',
}}
onSubmit={handleSubmit}>
{({ errors, touched }) => (
<Form>
<Field name="email">
{({ field, form }) => (
<label className="mt-3 block">
<span className="text-gray-700">Email</span>
<input
{...field}
type="text"
className="
mt-1
block
w-full
rounded-md
border-gray-300
shadow-sm
focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50
"
placeholder=""
style={
form.touched.email && form.errors.email ? (
{ border: '2px solid var(--primary-red)'}
) : null
}
/>
</label>
)}
</Field>
<Field name="password">
{({ field, form }) => (
<label className="mt-3 block">
<span className="text-gray-700">Password</span>
<input
{...field}
type="password"
className="
mt-1
block
w-full
rounded-md
border-gray-300
shadow-sm
focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50
"
placeholder=""
style={
form.touched.password && form.errors.password ? (
{ border: '2px solid var(--primary-red)'}
) : null
}
/>
</label>
)}
</Field>
<button className="btn btn-gr"
type="submit">
Submit
</button>
</Form>
)}
</Formik>
</div>
)
}
AuthContext.js
import React, { useEffect, useState } from 'react'
import { API } from "../api"
import axios from "axios"
import { isAfter, isEqual, parseISO, sub } from 'date-fns'
export const AuthContext = React.createContext(null)
export function AuthContextProvider({ children }) {
const [accessTokenExpiration, setAccessTokenExpiraton] = useState(undefined);
const getUser = () => {
return JSON.parse(localStorage.getItem('user'))
}
const isLoggedIn = () => {
return localStorage.getItem('user') !== null
}
const [user, setUser] = useState(() => {
return isLoggedIn() ? getUser() : null;
})
const [shouldGoToLogin, setShouldGoToLogin] = useState(() => {
if (!user || !user.access_token || !user.refresh_token) {
return true;
}
return false;
})
const logout = async () => {
if (!user) {
return;
}
const { access_token } = user;
localStorage.removeItem('user')
setUser(null);
return axios.post(API.auth.logout, {
headers: {
"Authorization": `Bearer ${access_token}`,
"Content-Type": "application/json"
},
withCredentials: true
});
}
const login = async (values) => {
console.log(values);
const correctedValues = { ...values, username: values.email };
return axios.post(API.auth.login, correctedValues)
.then(res => {
const data = res.data;
processApiData(data);
})
}
const refreshToken = async () => {
const user = getUser();
const redirectToLogout = () => {
localStorage.clear(); // Clear our localStorage
setShouldGoToLogin(true);
};
if (!user) { // No user
redirectToLogout();
}
console.log(API.auth.refreshToken);
const resp = await fetch(API.auth.refreshToken, {
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({'refresh': user?.refresh_token}),
method: "POST",
withCredentials: true
})
console.log("status", resp.status);
if (resp.status === 200) {
const data = await resp.json(); // Convert to JSON
console.log("refresh token data", data);
processApiData(data);
} else {
redirectToLogout();
}
}
const resetPassword = async (values) => {
return axios.post(API.auth.passwordReset, values);
}
const processApiData = (resp) => {
let newUser = { ...user, ...resp };
delete(newUser.user); // Delete the user sub-object since we merged that directly into the top-level object
saveUser(newUser); // Save the user
const { access_token_expiration } = newUser;
if (access_token_expiration) {
console.log("have expiration", access_token_expiration);
const nextExpiration = parseISO(access_token_expiration); // Convert from ISO 8601 to a Date Object
const earlyRefreshTime = sub(nextExpiration, { minutes: 55 }); // Do an hourish early
setAccessTokenExpiraton(earlyRefreshTime); // Set the upcoming expiraton
}
}
const saveUser = async (newUser) => {
localStorage.setItem('user', JSON.stringify(newUser))
setUser(newUser)
}
const signup = async (values) => {
return axios.post(API.auth.signup, values);
}
useEffect(() => {
if (!user) {
return;
}
const interval = setInterval(()=> {
if(!user){
return false;
}
if (accessTokenExpiration) {
const now = new Date(); // Get the current time
console.log(now);
console.log(accessTokenExpiration);
if (isAfter(now, accessTokenExpiration) || isEqual(now, accessTokenExpiration)) { // If we are late to the party or the stars have aligned
refreshToken(); // Refresh the token
}
} else { // We do not have an access token expiration yet
refreshToken(); // Refresh the token immediately so we get a time
}
}, 1000 * 15)
return ()=> clearInterval(interval)
}, [accessTokenExpiration, refreshToken, user])
return (
<AuthContext.Provider value={{
getUser,
isLoggedIn,
logout,
login,
resetPassword,
signup,
user,
shouldGoToLogin
}}>
{children}
</AuthContext.Provider>
)
}
My hunch is, that you are setting the shouldGoToLogin state in AuthContext.js in several places that get mixed up.
This might cause the EnforceAuthOnRoute function to run before shouldGoToLogin is set to false.
Try setting the default simply to true and handle the logic of when it is set to false somewhere else:
const [shouldGoToLogin, setShouldGoToLogin] = useState(true);
Alternatively try to have the handleSubmit await shouldGoToLogin to be false before calling navigate():
const { shouldGoToLogin } = useContext(AuthContext)
useEffect(() => {
if(shouldGoToLogin) navigate('/facilities');
},[shouldGoToLogin])
function handleSubmit(values) {
setLoading(true)
login(values).finally(() => setLoading(false))
Your question is bit unclear. As far as i have understood the question, here goes the following response.
If you want the reacter-router to work even when typed manually in the browser.
Do the following changes in the webpack server like this.
devServer: {
historyApiFallback: true,
contentBase: './',
hot: true
},
The historyApiFallback would fix the issue. Now routing would work correctly and you can refresh the page or type in the URL directly. The above answer works if you used Webpack.
For further details follow this link
I am making a simple SPA where you need to login before you can access other pages. I can successfully login and store the login data (firstname, lastname, etc.) cause I plan to use the data again later in the other pages. The problem is whenever I refresh the page, it always empty the state in the context which cause me to return to the login page. I am referring link for my SPA.
Do I need to do this? I would be thankful if someone can point out what I should change / improve. Thank you.
Here is my code.
App.js
import React, { useState } from "react";
import { BrowserRouter as Router, Link, Route } from "react-router-dom";
import { AuthContext } from "./context/auth";
import PrivateRoute from "./PrivateRoute";
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import Home from "./pages/Home";
import Admin from "./pages/Admin";
function App() {
const [authTokens, setAuthTokens] = useState();
const setTokens = (data) => {
// console.log("DATA ",data);
localStorage.setItem("tokens", JSON.stringify(data));
setAuthTokens(data);
}
// console.log(authTokens);
return (
<AuthContext.Provider value={{ authTokens, setAuthTokens: setTokens }}>
<Router>
<div className="app">
<ul>
<li><Link to="/">Home Page</Link></li>
<li><Link to="/admin">Admin Page</Link></li>
</ul>
<Route exact path="/login" component={Login} />
<Route exact path="/signup" component={Signup} />
<Route exact path="/" component={Home} />
<PrivateRoute exact path="/admin" component={Admin} />
</div>
</Router>
</AuthContext.Provider>
);
}
export default App;
Login.js
import React, { useState } from "react";
import axios from "axios";
import { Link, Redirect } from "react-router-dom";
import { useAuth } from "../context/auth";
import { Card, Form, Input, Button, Error } from "../components/AuthForm";
const Login = () => {
const [isLoggedIn, setLoggedIn] = useState(false);
const [isError, setIsError] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { setAuthTokens } = useAuth();
const handleLogin = () => {
axios
.post("LOGINLINK", {
email,
password,
})
.then((result) => {
if (result.status === 200) {
setAuthTokens(result.data);
setLoggedIn(true);
} else {
setIsError(true);
}
})
.catch((error) => {
setIsError(true);
});
};
if (isLoggedIn) {
return <Redirect to="/" />;
}
return (
<Card>
<Form>
<Input
type="email"
placeholder="Email"
value={email}
onChange={(e) => {
setEmail(e.target.value);
}}
/>
<Input
type="password"
placeholder="password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
<Button onClick={handleLogin}>Login</Button>
</Form>
<Link to="/signup">Don't have an account?</Link>
{isError && (
<Error>The username or password provided were incorrect!</Error>
)}
</Card>
);
};
export default Login;
Auth.js
import { createContext, useContext } from "react";
export const AuthContext = createContext();
export function useAuth() {
console.log("CONTEXT", useContext(AuthContext));
return useContext(AuthContext);
}
In your App component you need to fetch the data from localStorage when initializing your state so it has some data to start with.
const localToken = JSON.parse(localStorage.getItem("tokens"));
const [authTokens, setAuthTokens] = useState(localToken);
If user has already authenticated it will be available in localStorage else it's going to be null.
I also had same problem but I solved liked this Don't use localStorage directly use your state and if it is undefined then only use localStorage. cause directly manipulating state with localStorage is in contrast with react internal state and effects re-render .
const getToken = () => {
JSON.parse(localStorage.getItem('yourtoken') || '')
}
const setToken = (token) => {
localStorage.setItem('key' , token)
}
const [authTokens, setAuthTokens] = useState(getToken());
const setTokens = (data) => {
// console.log("DATA ",data);
setToken(token);
setAuthTokens(data);
}
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;
I making a fact generator website and I have a generate button on my homepage, when a user is logged in I want to track how many times they have clicked the button and later display it on their homepage. I am very new to React and Firebase so if any additional information is needed please inform me.
My app.js (has firebase functions & generate button)
import React, { useState, useEffect } from "react";
import "./App.css";
import BackGround from "./components/BG";
import FactGen from "./components/FactGen";
import Navbar from "./components/Navbar";
import {
BrowserRouter as Router,
Switch,
Route,
} from "react-router-dom";
import About from "./components/About";
import NoMatchPage from "./components/NoMatchPage";
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
import Login from "./components/Login";
import fire from "./components/fire";
import Dashboard from "./components/Dashboard";
const App = () => {
const [user, setUser] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const [hasAccount, setHasAccount] = useState(false);
const clearInputs = () => {
setEmail("");
setPassword("");
};
const clearErrors = () => {
setEmailError("");
setPasswordError("");
};
const handleLogin = () => {
clearErrors();
fire
.auth()
.signInWithEmailAndPassword(email, password)
.catch((err) => {
switch (err.code) {
case "auth/invalid-email":
case "auth/user-disabled":
case "auth/user-not-found":
setEmailError(err.message);
break;
case "auth/wrong-password":
setPasswordError(err.message);
break;
}
});
};
const handleSignup = () => {
clearErrors();
fire
.auth()
.createUserWithEmailAndPassword(email, password)
.catch((err) => {
switch (err.code) {
case "auth/email-already-in-use":
case "auth/invalid-email":
setEmailError(err.message);
break;
case "auth/weak-password":
setPasswordError(err.message);
break;
}
});
};
const handleLogout = () => {
fire.auth().signOut();
};
const authListener = () => {
fire.auth().onAuthStateChanged((user) => {
if (user) {
clearInputs();
setUser(user);
} else {
setUser("");
}
});
};
useEffect(() => {
authListener();
}, []);
return (
<Router>
<div className="App">
<BackGround />
<Navbar />
<Switch>
{/* <Route path="/Userconnections" component={Userconnections} /> */}
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route
path="/Auth"
render={(props) => (
<Login
email={email}
setEmail={setEmail}
password={password}
setPassword={setPassword}
handleLogin={handleLogin}
hasAccount={hasAccount}
setHasAccount={setHasAccount}
emailError={emailError}
passwordError={passwordError}
user={user}
handleSignup={handleSignup}
/>
)}
/>
<Route
path="/dashboard"
render={(props) => (
<Dashboard handleLogout={handleLogout} user={user} />
)}
/>
<Route component={NoMatchPage} />
</Switch>
</div>
</Router>
);
};
const Home = () => {
const [refresh, setRefresh] = useState(0);
const refresher = () => {
setRefresh(refresh + 1);
console.log(refresh);
};
return (
<div className="App">
<FactGen key={refresh} />
<center>
<button className="generate-button" onClick={refresher}>
Generate
</button>
</center>
</div>
);
};
export default App;
My dashboard.js (Want it to display the value where it says 100 on line 14 right now)
import React from "react";
import { BrowserRouter as Router, useHistory } from "react-router-dom";
const Dashboard = ({ handleLogout, user }) => {
const history = useHistory();
function homeRedirect() {
history.push("/");
}
return (
<Router>
{user ? null : homeRedirect()}
<div>
<h1 className="welcome-text">Welcome, User</h1>
<h1 className="page-header">You have generated 100 Facts!</h1>
<center>
<button className="logout-button" onClick={handleLogout}>
Logout
</button>
</center>
</div>
</Router>
);
};
export default Dashboard;
Any help would be greatly appreciated!
You can achieve this by creating a state variable on the parent component and passing the callback to the child:
// Parent.js :
const [counter, setCounter] = useState(0)
...
return (
...
<ChildComponent setCounter={setCounter} counter={counter} />
<p> You have clicked the button {counter} times </p>
...
)
Then, since you pass the callback, you can do this in your ChildComponenet
//Child.js
const Child = (props) => {
const { setCounter, counter } = props
return (
...
<button onClick={() => setCounter(counter + 1)}> Counter Button </button>
...
)
}