I have a simple blog site I am trying to create. However, once I login and try to navigate to a different component of the site, I get taken back to the Login page. I think there’s a disconnect as far as how to use useContext correctly. I followed this video and had to stop following along when he started using authService because i wasn’t sure where exactly he was getting it from. MY code:
App.js
import './App.css';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import LoggedIn from './NavBar/LoggedIn';
import Home from './Component/Home';
import { About } from './Component/About';
import { Blog } from './Component/Blog';
import { Products } from './Component/Products';
import { Pricing } from './Component/Pricing';
import { UserContext } from './utils/UserContext';
import { LoginPage } from './Component/LoginPage';
import { AuthProvider, useAuth } from './utils/UserContext'
import PrivateRoute from './Component/PrivateRoute'
function App() {
return (
<>
<BrowserRouter>
<AuthProvider>
<PrivateRoute>
<LoggedIn />
</PrivateRoute>
<Routes>
<Route path='/' element={<LoginPage />} />
<Route path='/home' element={<Home />} />
<Route path='/about' element={<About />} />
<Route path='/blog' element={<Blog />} />
<Route path='/products' element={<Products />} />
<Route path='/pricing' element={<Pricing />} />
</Routes>
</AuthProvider>
</BrowserRouter>
</>
);
}
export default App;
UserContext.js
import React, { useState, useEffect, useContext, createContext } from "react";
const UserContext = createContext();
export function useAuth(){
return useContext(UserContext)
}
export function AuthProvider(props){
const [authUser, setAuthUser] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const value = {
authUser,
setAuthUser,
isLoggedIn,
setIsLoggedIn
}
return <UserContext.Provider value={value}>{props.children}</UserContext.Provider>
LoginPage.js
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button'
import '../CSS/LoginPageCSS.css'
import { useAuth } from '../utils/UserContext';
export function LoginPage() {
const navigate = useNavigate();
const {
authUser, setAuthUser, isLoggedIn, setIsLoggedIn
} = useAuth()
const [loginAttempts, setLoginAttempts] = useState(3);
const handleLogin = (event) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
validateLogin(data.get("username"), data.get("password"));
// console.log({
// email: data.get("username"),
// password: data.get("password"),
// });
}
const validateLogin = async (username, password) => {
const res = await fetch(`http://localhost:8000/api/users/${username}`);
const data = await res.json();
const pswrd = data.user.password;
if (password !== pswrd) {
setLoginAttempts(loginAttempts => loginAttempts - 1);
console.log("ATTMEPTS:", loginAttempts, "Password", password, "pswrd:", pswrd);
} else {
setIsLoggedIn(true);
setAuthUser(username);
navigate('/home');
}
}
return (
<div>
{
loginAttempts > 0 ? (
<div className='LoginPage_container'>
<div>
<h2>Welcome to THE SPACE</h2>
<p>Login to get started</p>
</div>
<Box
onSubmit={handleLogin}
component="form"
sx={{
'& .MuiTextField-root': { m: 1, width: '25ch' },
}}
noValidate
autoComplete="off"
>
<div>
<TextField
id="username"
name="username"
label="Username"
/>
<TextField
id="password"
name="password"
label="Password"
/>
</div>
<Button
name="password"
type="submit"
variant="contained"
>
Sign In
</Button>
</Box>
</div>
) : <h1>Too many attempts, aborting</h1>
}
</div>
)
}
LoggedIn.js (redirected to after login is verified. A protected NAvBar that is only able to be seen if user is loggedin)
import React, { useEffect, useState } from "react";
import AppBar from "@mui/material/AppBar";
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import Menu from "@mui/material/Menu";
import MenuIcon from "@mui/icons-material/Menu";
import Container from "@mui/material/Container";
import Avatar from "@mui/material/Avatar";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import MenuItem from "@mui/material/MenuItem";
import AdbIcon from "@mui/icons-material/Adb";
import Badge from "@mui/material/Badge";
import NotificationsIcon from "@mui/icons-material/Notifications";
import { Link } from "react-router-dom";
import { useContext } from 'react';
function LoggedIn( { authUser } ) {
const style = {
textDecoration: "none",
color: "white",
};
const home = <Link to={`/home`} style={style}>Home</Link>
const products = <Link to={`/products`} style={style}>Products</Link>
const pricing = <Link to={`/pricing`} style={style}>Pricing</Link>
const blog = <Link to={`/blog`} style={style}>Blog</Link>
const about = <Link to={`/about`} style={style}>About</Link>
const profile = <Link to={`/profile`} style={style}>Profile</Link>
const account = <Link to={`/account`} style={style}>Account</Link>
const dashboard = <Link to={`/dashboard`} style={style}>Dashboard</Link>
const logout = <Link to={`/logout`} style={style}>Logout</Link>
const pages = [home, products, pricing, blog, about];
const settings = [home, profile, account, dashboard, logout];
const [anchorElNav, setAnchorElNav] = React.useState(null);
const [anchorElUser, setAnchorElUser] = React.useState(null);
const handleOpenNavMenu = (event) => {
setAnchorElNav(event.currentTarget);
};
const handleOpenUserMenu = (event) => {
setAnchorElUser(event.currentTarget);
};
const handleCloseNavMenu = () => {
setAnchorElNav(null);
};
const handleCloseUserMenu = () => {
setAnchorElUser(null);
};
const [userData, setUserData] = useState('');
const getUserData = async () => {
const res = await fetch(`http://localhost:8000/api/users/${authUser}`);
const data = await res.json();
const user_data = data.user;
console.log("DATA:", user_data)
setUserData(user_data)
};
useEffect(() => {
getUserData();
}, []);
return (
<AppBar position="static">
<Container maxWidth="xl">
<Toolbar disableGutters>
<AdbIcon sx={{ display: { xs: "none", md: "flex" }, mr: 1 }} />
<Typography
variant="h6"
noWrap
component="a"
**HERE IS WHERE IT WONT REDIRECT TO**
href="/blog"
sx={{
mr: 2,
display: { xs: "none", md: "flex" },
fontFamily: "monospace",
fontWeight: 700,
letterSpacing: ".3rem",
color: "inherit",
textDecoration: "none",
}}
>
{userData.user_name}
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: "flex", md: "none" } }}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleOpenNavMenu}
color="inherit"
>
<MenuIcon />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
sx={{
display: { xs: "block", md: "none" },
}}
>
{pages.map((page) => (
<MenuItem key={page} onClick={handleCloseNavMenu}>
<Typography textAlign="center">{page}</Typography>
</MenuItem>
))}
</Menu>
</Box>
<AdbIcon sx={{ display: { xs: "flex", md: "none" }, mr: 1 }} />
<Typography
variant="h5"
noWrap
component="a"
href=""
sx={{
mr: 2,
display: { xs: "flex", md: "none" },
flexGrow: 1,
fontFamily: "monospace",
fontWeight: 700,
letterSpacing: ".3rem",
color: "inherit",
textDecoration: "none",
}}
>
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: "none", md: "flex" } }}>
{pages.map((page) => (
<Button
key={page}
onClick={handleCloseNavMenu}
sx={{ my: 2, color: "white", display: "block" }}
>
{page}
</Button>
))}
</Box>
<Box sx={{ flexGrow: 0 }}>
<Tooltip title="Open settings">
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<Avatar
alt={userData.user_name}
src={userData.profile_pic}
/>
</IconButton>
</Tooltip>
<Menu
sx={{ mt: "45px" }}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
{settings.map((setting) => (
<MenuItem key={setting} onClick={handleCloseUserMenu}>
<Typography textAlign="center">{setting}</Typography>
</MenuItem>
))}
</Menu>
</Box>
</Toolbar>
</Container>
</AppBar>
);
}
export default LoggedIn;
PrivateRoute.js
import React, { useState, cloneElement, useEffect } from 'react'
import { Navigate } from 'react-router-dom'
import { useAuth } from '../utils/UserContext';
const PrivateRoute = ({ children }) => {
const {
authUser, setAuthUser, isLoggedIn, setIsLoggedIn
} = useAuth()
if (isLoggedIn) return (React.cloneElement(children, {authUser}));
return <Navigate to='/' />
}
export default PrivateRoute;
I have a feeling I am missing a useEffect somewhere but dont know how or where to implement it. Any help would be greatly appreciated.
2
Answers
Firstly, this article helped a lot. What i eventually ended doing was making the LoggedIn component its own private route and wrapping that around home. That was the work around to get my context passed down and eventually being able to redirrect to another component and context persisting.
LoggedIn.jsx
App.js
NOTE: notice the
<Route path='/home' element={<LoggedIn><Home /></LoggedIn>} />
in App.js. I will do this for the other route paths. For the sake of my ego, I hope i was not the only one who did not know that you could wrap a component around another component in this use case but it works! Notice I passed inchildren
as a prop to LoggedIn and rendered it after the rest of my code. You might be wondering why I just didnt render the LoggedIn component on top of all my pages BUT we're not gonna talk about that. Learned a lot in the process.The issue seems to be with the usage of the
PrivateRoute
component. ThePrivateRoute
component is wrapping theLoggedIn
component, but it should be wrapping the components that need authentication. Additionally, thePrivateRoute
component is not correctly checking if the user is authenticated before rendering the component.Here’s a possible solution:
App.js
As you can see, we moved the
PrivateRoute
component inside the Routes component and we added theLoggedIn
component to each of the protected routes.PrivateRoute.js
Also, we updated the
PrivateRoute
component to check if the user is authenticated before rendering the children components. If the user is not authenticated, it will redirect to the login page.With these changes, the
useContext
should be persisted throughout all components.