I'm trying to make a private route from my login and signup pages to my dashboard page, but all the tutorials and guides that I've stumbled upon all require some sorta AuthContext thing and I didn't implement my authentication procedure using AuthContext.
I've tried different ways but none of them work and just end up giving me a blank page when I get to the dashboard page, what can I do to make it a private route? Using Firebase v9 btw.
SignUp.js
import './Signup.css'
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { auth }from '../../../firebase';
import { createUserWithEmailAndPassword, onAuthStateChanged } from 'firebase/auth';
import { Typography, Container, TextField, Button, Alert } from '#mui/material';
const Signup = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState('');
const [user, setUser] = useState({});
const history = useHistory();
onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
})
const signup = async (e) => {
e.preventDefault();
if (password !== confirmPassword) {
return setError("Passwords do not match")
}
try {
const user = await createUserWithEmailAndPassword(
auth,
email,
password
);
history.push("/dashboard/canvas");
} catch (err) {
setError(err.message);
}
}
return (
<>
<div className="text-div">
<Typography textAlign="center" variant="h3">Create a new account</Typography>
</div>
<Container className="cont" maxWidth="xl" sx={{ backgroundColor: "#ffffff", width: 500, height: "auto", borderRadius: 4, marginTop: 5, display: "flex", flexDirection: "column", padding: 5, }}>
{ error && <Alert severity="error">{error}</Alert> }
<TextField label="Email" margin="dense" type="email" onChange={ (e) => {
setEmail(e.target.value);
}}/>
<TextField label="Password" margin="dense" type="password" onChange={ (e) => {
setPassword(e.target.value);
}}/>
<TextField label="Confirm Password" margin="dense" type="password" onChange={ (e) => {
setConfirmPassword(e.target.value);
}}/>
<Button onClick={signup} variant="contained" sx={{ marginTop: 2, }}>Sign Up</Button>
<div>
Already have an account? <Link to="/login" style={{ color: '#000' }}>Log In</Link>
</div>
</Container>
</>
)
}
export default Signup;
Login.js
import './Login.css'
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth }from '../../../firebase';
import { Typography, Container, TextField, Button, Alert } from '#mui/material';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const history = useHistory();
const login = async () => {
try {
const user = await signInWithEmailAndPassword(
auth,
email,
password
);
//alert("Success, user is recognized");
history.push("/dashboard/canvas");
} catch (err) {
setError("The email or password you entered is incorrect");
}
}
return (
<>
<div className="text-div">
<Typography textAlign="center" variant="h3">Login</Typography>
</div>
<Container className="cont" maxWidth="xl" sx={{ backgroundColor: "#ffffff", width: 500, height: "auto", borderRadius: 4, marginTop: 5, display: "flex", flexDirection: "column", padding: 5, }}>
{ error && <Alert severity="error">{error}</Alert> }
<TextField label="Email" margin="dense" type="email" onChange={(e) => {
setEmail(e.target.value);
}}/>
<TextField label="Password" margin="dense" type="password" onChange={(e) => {
setPassword(e.target.value);
}}/>
<Button onClick={login} variant="contained" sx={{ marginTop: 2, }}>Login</Button>
<div>
Don't have an account? <Link to="/signup" style={{ color: '#000' }}>Create one here</Link>
</div>
<div>
<Link to="/request-password-reset" style={{ color: '#000' }}>Forgot your password?</Link>
</div>
</Container>
</>
)
}
export default Login;
firebase.js
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
const firebaseConfig = {
apiKey,
authDomain,
projectId,
storageBucket,
messagingSenderId,
appId,
measurementId
}
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
export {
auth
};
App.js
import './App.css';
import Home from './components/Pages/Home';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Signup from './components/Pages/Signup/Signup';
import Login from './components/Pages/Login/Login';
import UserDashboard from './components/Pages/UserDashboard/UserDashboard';
import ForgotPassword from './components/Pages/Forgot-Password';
function App() {
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/signup" component={Signup}/>
<Route path="/login" component={Login}/>
<Route path="/dashboard" component={UserDashboard}/>
<Route path="/request-password-reset" component={ForgotPassword}/>
</Switch>
</div>
</Router>
);
}
export default App;
If you are trying to create a private route component without persisting the authentication state somewhere in your app and exposed out via a React context then you will need to check the auth status asynchronously on each route change. This means you'll also need a "loading" or "pending" state while the auth status check occurring.
Here's an example implementation of just a custom private route sans any persisted state.
import { useEffect, useState } from 'react';
import { Route, Redirect } from 'react-router-dom'; // v4/5
import { onAuthStateChanged } from 'firebase/auth';
import { auth }from '../../../firebase';
const PrivateRoute = props => {
const [pending, setPending] = useState(true);
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsubscribe = onAuthStateChanged(
auth,
user => {
setCurrentUser(user);
setPending(false);
},
error => {
// any error logging, etc...
setPending(false);
}
);
return unsubscribe; // <-- clean up subscription
}, []);
if (pending) return null; // don't do anything yet
return currentUser
? <Route {...props} /> // <-- render route and component
: <Redirect to="/login" />; // <-- redirect to log in
};
react-router-dom#6
Custom route components are out in v6, use a layout route. The PrivateRoute component will replace Route with Outlet for nested routes to render their matched element prop into, and Navigate replaces Redirect.
import { useEffect, useState } from 'react';
import { Outlet, Navigate } from 'react-router-dom'; // v4/5
import { onAuthStateChanged } from 'firebase/auth';
import { auth }from '../../../firebase';
const PrivateRoute = props => {
const [pending, setPending] = useState(true);
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsubscribe = onAuthStateChanged(
auth,
user => {
setCurrentUser(user);
setPending(false);
},
error => {
// any error logging, etc...
setPending(false);
}
);
return unsubscribe; // <-- clean up subscription
}, []);
if (pending) return null; // don't do anything yet
return currentUser
? <Outlet /> // <-- render outlet for routes
: <Navigate to="/login" replace />; // <-- redirect to log in
};
Wrap the routes you want to protect.
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} />
<Route path="/request-password-reset" element={<ForgotPassword />} />
<Route element={<PrivateRoute />}>
<Route path="/dashboard" element={<UserDashboard />} />
</Route>
</Routes>
</Router>
);
}
Related
I'm new to React so I've been following a lot of tutorials to understand certain functionality. Currently I've implemented bottom navigation but I can't find a solution on only displaying bottom navigation after I log in.
Here's my app.js code:
import { Container } from 'react-bootstrap';
import Login from './components/Login';
import Register from './components/Register';
import SearchPage from './components/SearchPage';
import Profile from './components/Profile';
import { AuthProvider } from './context/AuthContext';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import React, { useState } from "react";
import MuiBottomNav from './components/MuiBottomNav';
function App() {
return (
<>
<Container className='d-flex align-items-items-center'
style={{ minHeight: "100vh" }}>
<div className='w-100' style={{ maxWidth: "400px" }}>
<Router>
<AuthProvider>
<MuiBottomNav />
<Routes>
<Route exact path="/" element={<SearchPage />} />
<Route path='/register' element={<Register />} />
<Route path='/login' element={<Login />} />
<Route path='/profile' element={<Profile />} />
</Routes>
</AuthProvider>
</Router>
</div>
</Container>
</>
)
}
export default App;
Here's the bottom nav component
import AddHomeIcon from '#mui/icons-material/AddHome'
import SearchIcon from '#mui/icons-material/Search';
import { BottomNavigation, BottomNavigationAction } from "#mui/material";
import React, { useState } from "react";
import { useNavigate } from 'react-router-dom';
const MuiBottomNav = () => {
const [bnValue, setBnValue] = useState(0);
const navigate = useNavigate();
return (
<div>
<BottomNavigation
sx={{ width: "100%", position: "absolute", bottom: 0 }}
value={bnValue}
onChange={(event, value) => setBnValue(value)}
>
<BottomNavigationAction
label="Profile"
value={bnValue}
onClick={() => navigate("/profile")}
icon={<AddHomeIcon />}
/>
<BottomNavigationAction
label="Search"
value={bnValue}
onClick={() => navigate("/")}
icon={<SearchIcon />}
/>
</BottomNavigation>
</div>
)
}
export default MuiBottomNav
At the moment the bottom nav is displayed even on the Register and Login page. I'd like to only see it after logging in.
import React, { useContext, useEffect, useState } from "react";
import { auth } from '../firebase'
const AuthContext = React.createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
function logout() {
return auth.signOut()
}
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>
)
}
The MuiBottomNav can access the AuthContext and conditionally render the nav contents if there is a truthy currentUser.
const MuiBottomNav = () => {
const { currentUser } = useAuth(); // <-- access context
const [bnValue, setBnValue] = useState(0);
const navigate = useNavigate();
if (!currentUser) return null; // <-- no user, return null
return (
<div>
<BottomNavigation
sx={{ width: "100%", position: "absolute", bottom: 0 }}
value={bnValue}
onChange={(event, value) => setBnValue(value)}
>
<BottomNavigationAction
label="Profile"
value={bnValue}
onClick={() => navigate("/profile")}
icon={<AddHomeIcon />}
/>
<BottomNavigationAction
label="Search"
value={bnValue}
onClick={() => navigate("/")}
icon={<SearchIcon />}
/>
</BottomNavigation>
</div>
);
}
I'm building a new web application utilizing a ASP.NET Core backend and React frontend. I've built many sites using "traditional" pre-React web technologies and for this project wanted to dig into React more deeply. I built some demo sites that were very basic in React several years ago but am otherwise new to React and am having some issues "connecting the dots" in my mind on how things are supposed to hook up.
At the moment I'm focused on getting the basics of the frontend going with the basics: login, logout, registration, and private routes to make sure the user is logged in before seeing the internal pages. I'm using MUI for visual components and React Router to handle the routing. I opted to try to use contexts to handle storing auth-related data instead of props since that seems to be what folks recommend for more flexibility/reusability. My code files are as follows:
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import CssBaseline from '#mui/material/CssBaseline';
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import './index.css';
import AuthProvider from './components/AuthProvider'
import ProtectedRoute from './components/ProtectedRoute'
import Admin from "./pages/Admin";
import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";
import Register from "./pages/Register";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<CssBaseline />
<BrowserRouter>
<AuthProvider>
<Routes>
<Route index element={<Navigate to="/dashboard" />} />
<Route path="login" element={<Login />} />
<Route path="dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
<Route path="admin" element={<ProtectedRoute><Admin /></ProtectedRoute>} />
<Route path="register" element={<Register />} />
</Routes>
</AuthProvider>
</BrowserRouter>
</React.StrictMode>
);
Login.js
import { Box, Button, Container, Grid, Link, TextField, Typography } from '#mui/material';
import { Controller, useForm } from "react-hook-form";
import { useAuth } from "../components/ProtectedRoute"
const Login = () => {
const { onLogin } = useAuth();
const { handleSubmit, control, formState: { errors } } = useForm();
const onSubmit = (values) => {
console.log(values);
};
return (
<Box component="main"
sx={{
alignItems: 'center',
display: 'flex',
flexGrow: 1,
minHeight: '100%'
}}
>
<Container maxWidth="sm">
<Box sx={{ my: 3 }}>
<Typography color="textPrimary" variant="h4">
Sign in
</Typography>
<Typography color="textSecondary" gutterBottom variant="body2">
Sign in on the internal platform
</Typography>
</Box>
<Grid container spacing={3}>
<form type="POST" onSubmit={handleSubmit(onSubmit)}>
<Controller
render={({ field }) => (
<TextField
{...field}
fullWidth
margin="normal"
label="Email Address"
type="email"
error={Boolean(errors["email"])}
helperText={errors["email"] ? errors["email"].message : ""}
/>
)}
control={control}
name="email"
defaultValue=""
rules={{ required: { value: true, message: "An email address must be entered" } }}
/>
<Controller
render={({ field }) => (
<TextField
{...field}
fullWidth
margin="normal"
label="Password"
type="password"
error={Boolean(errors["password"])}
helperText={errors["password"] ? errors["password"].message : ""}
/>
)}
control={control}
name="password"
defaultValue=""
rules={{ required: { value: true, message: "A password must be entered" } }}
/>
<Box sx={{ py: 2 }}>
<Button
color="primary"
fullWidth
size="large"
type="submit"
variant="contained"
onClick={onLogin}
>
Sign In Now
</Button>
</Box>
<Typography color="textSecondary" variant="body2">
Don't have an account?
{' '}
<Link
href="/register"
variant="subtitle2"
underline="hover"
sx={{ cursor: 'pointer' }}
>
Sign Up
</Link>
</Typography>
</form>
</Grid>
</Container>
</Box>
)
};
export default Login;
Dashboard.js
import { NavLink } from "react-router-dom";
import { useAuth } from "../components/ProtectedRoute"
const Dashboard = () => {
const { token, onLogout } = useAuth();
return (
<div>
<nav>
<NavLink to="/dashboard">Dashboard</NavLink>
<NavLink to="/admin">Admin</NavLink>
{token && (
<button type="button" onClick={onLogout}>
Sign Out
</button>
)}
</nav>
<p>Welcome to your Dashboard. Authenticated as {token}</p>
</div>
);
};
export default Dashboard;
AuthContext.js
import * as React from 'react';
const AuthContext = React.createContext(null);
export default AuthContext;
AuthProvider.js
import * as React from 'react';
import AuthContext from '../contexts/AuthContext'
import {
useNavigate,
useLocation
} from 'react-router-dom';
//const users = [{ username: "test#test.com", password: "testpassword" }];
const fakeAuth = () =>
new Promise((resolve) => {
//const account = users.find((user) => user.username === email);
//if (account && account.password === password) {
// console.log("Authenticated");
setTimeout(() => resolve('1234f2f1d131rf12'), 250);
//}
});
const AuthProvider = ({ children }) => {
const navigate = useNavigate();
const location = useLocation();
const [token, setToken] = React.useState(null);
const handleLogin = async () => {
// Implement remote API call instead of mock promise
// --> How to get user/password here for use sending to auth endpoint?
const token = await fakeAuth();
setToken(token);
const origin = location.state?.from?.pathname || '/dashboard';
navigate(origin);
};
const handleLogout = () => {
setToken(null);
};
const value = {
token,
onLogin: handleLogin,
onLogout: handleLogout,
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
export default AuthProvider;
ProtectedRoute.js
import * as React from 'react';
import {
Navigate,
useLocation
} from 'react-router-dom';
import AuthContext from '../contexts/AuthContext'
export const useAuth = () => {
return React.useContext(AuthContext);
};
const ProtectedRoute = ({ children }) => {
const { token } = useAuth();
const location = useLocation();
if (!token) {
return <Navigate to="/login" replace state={{ from: location }} />;
}
return children;
};
export default ProtectedRoute;
Within AuthProvider.js there's a handleLogin binding that does get called correctly and currently I have it set up with just a mock call returning a placeholder pseudo-token. The problem is, I don't know how to get the submitted username and password from the login form to get passed over into this handler to then send along to a real authentication endpoint. Other than this, it seems the login process works to protect the "private" routes and perform login/logout checks.
If someone could assist in helping me connect the dots so I can get the user/pass values into the handleLogin function that'd be great. There are many tutorials out there on React but there are so many with a huge variety of 3rd party parts and differing styles that finding one that's applicable to my problem has been difficult. I'm guessing I'm missing some key part but for the life of me I can't figure it out.
I am learning firebase. I have a button log out. When I am clicking on the button it is not navigating to login page.
I have set routing with user.emailVerified value.
Nav.jsx
import { getAuth, signOut } from 'firebase/auth';
import React from 'react';
import { useDispatch } from 'react-redux';
import { Link, useNavigate } from 'react-router-dom';
import { setLogOut } from '../redux/slice';
const Nav = () => {
const navigate = useNavigate();
const auth = getAuth();
const dispatch = useDispatch()
const logOutHandler = () => {
signOut(auth)
.then(() => {
dispatch(setLogOut())
navigate('/login', { replace: true });
})
.catch(error => {
console.log(error);
});
};
return (
<nav className="navbar navbar-expand-lg bg-light ">
<div className="container">
<Link className="navbar-brand" to="/">
Navbar
</Link>
<Link className="nav-link" to="/">
Home
</Link>
<button className="btn btn-outline-secondary" onClick={logOutHandler}>
Log Out
</button>
</div>
</nav>
);
};
export default Nav;
App.jsx
import React, { useState } from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/js/bootstrap.min.js';
import 'bootstrap-icons/font/bootstrap-icons.css';
import { Routes, Route, Navigate } from 'react-router-dom';
import Nav from './components/Nav';
import Home from './pages/Home';
import Login from './pages/Login';
import SignUp from './pages/SignUp';
import EmailVerify from './pages/EmailVerify';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
const App = () => {
const [verified, setverified] = useState();
// get user information
const auth = getAuth();
onAuthStateChanged(auth, user => {
if (user) {
setverified(user.emailVerified);
} else {
// User is signed out
// ...
}
});
let routes;
if (verified) {
routes = (
<>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</>
);
} else {
routes = (
<>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<SignUp />} />
<Route path="/email-verify" element={<EmailVerify />} />
<Route path="*" element={<Navigate to="/login" replace />} />
</Routes>
</>
);
}
return <>{routes}</>;
};
export default App;
redux
import { createSlice } from '#reduxjs/toolkit';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
const initialState = {
email: JSON.parse(localStorage.getItem('email')),
isLoggedIn: JSON.parse(localStorage.getItem('loggedInStatus')),
};
export const slice = createSlice({
name: 'slice',
initialState,
reducers: {
setLogInValue: (state, action) => {
localStorage.setItem('email', JSON.stringify(action.payload));
localStorage.setItem('loggedInStatus', JSON.stringify(true));
state.email = action.payload;
state.isLoggedIn = true;
},
setLogOut: state => {
localStorage.setItem('email', null);
localStorage.setItem('loggedInStatus', false);
state.email = null;
state.isLoggedIn = false;
},
},
});
export const { setLogInValue, setLogOut } = slice.actions;
export default slice.reducer;
I am wanting to go login page when I click on log out button because when I am clicking on logout it is user is null then. But why not navigating to login page? How can I do that?
I have added setverified(false) on else condition of the onAuthStateChanged on App.jsx and it is working as I wanted.
Updated portion:
onAuthStateChanged(auth, user => {
if (user) {
setverified(user.emailVerified);
} else {
setverified(false); // updated line
}
});
I am trying to make it so that when a user clicks on a certain Link (Upload Component), it will redirect them to to the login component and am not sure how to accomplish this task in React. I was directed to another answered question, but it hasn't helped me as I am still confused on what I need to do with my own set up. I understand I need to make my own protected route (maybe), but I saw others accessing useContext and I do not have any file with context. I am using Version 6 in react dom. I am also using React router and redux in my project so I know I need to access the state somehow, just not sure how to wrap my mind around it so if anyone could help, I would appreciate it. The user is being stored in local storage with JWT authentication.
App.js:
import React, {useState, useEffect, useCallback} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom'; //Switch was replaced by Routes in react-router-dom v6
import './App.css';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import Home from './components/pages/Home';
import Uploads from './components/pages/Uploads';
import Account from './components/pages/Account';
import Login from './components/pages/Login';
import LogOut from './components/Logout';
import Register from './components/pages/Register';
import Profile from './components/pages/Profile';
import EditProfile from './components/pages/EditProfile';
import BoardAdmin from './components/pages/BoardAdmin';
import BoardModerator from './components/pages/BoardModerator';
import BoardUser from './components/pages/BoardUser';
import {logout} from './slices/auth';
import EventBus from './common/EventBus';
const App = () => {
const [showModeratorBoard, setShowModeratorBoard] = useState(false);
const [showAdminBoard, setShowAdminBoard] = useState(false);
const {user: currentUser} = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
if (currentUser) {
setShowModeratorBoard(currentUser.roles.includes('ROLE_MODERATOR'));
setShowAdminBoard(currentUser.roles.includes('ROLE_ADMIN'));
} else {
setShowModeratorBoard(false);
setShowAdminBoard(false);
}
}, [currentUser]);
return (
<>
<Router>
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
<Route path='/create/upload' element={<Uploads />} />
<Route path='/my-account' element={<Account />} />
<Route path='/login' element={<Login />} />
<Route path='/logout' element={<LogOut />} />
<Route path='/register' element={<Register />} />
<Route path='/profile' element={<Profile />} />
<Route path='/profile/edit' element={<EditProfile />} />
<Route path='/user' element={<BoardUser />} />
<Route path='/mod' element={<BoardModerator />} />
<Route path='/admin' element={<BoardAdmin />} />
</Routes>
<Footer />
</Router>
</>
);
};
export default App;
Auth.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {setMessage} from './messages';
import AuthService from '../services/auth.service';
const user = JSON.parse(localStorage.getItem('user'));
export const register = createAsyncThunk(
'auth/register',
async ({username, email, password}, thunkAPI) => {
try {
const response = await AuthService.register(username, email, password);
thunkAPI.dispatch(setMessage(response.data.message));
return response.data;
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const login = createAsyncThunk(
'auth/login',
async ({username, password}, thunkAPI) => {
try {
const data = await AuthService.login(username, password);
return {user: data};
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const logout = createAsyncThunk('auth/logout', async () => {
await AuthService.logout();
});
const initialState = user
? {isLoggedIn: true, user}
: {isLoggedIn: false, user: null};
const authSlice = createSlice({
name: 'auth',
initialState,
extraReducers: {
[register.fulfilled]: (state, action) => {
state.isLoggedIn = false;
},
[register.rejected]: (state, action) => {
state.isLoggedIn = false;
},
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
},
});
const {reducer} = authSlice;
export default reducer;
It looks like adding the if statement in my upload file is making it so that a logged in user can access the h1 element that is in the file, but not an unauthenticated user. So its working to an extent, just not redirecting back to login.
Upload.jsx:
import React from 'react';
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux';
function Uploads() {
const {user: currentUser} = useSelector((state) => state.auth);
if (!currentUser) {
return <Link to='/login' />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Rendering the Link doesn't imperatively navigate, use the Navigation component.
Example:
import React, { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
function Uploads() {
const { user: currentUser } = useSelector((state) => state.auth);
if (!currentUser) {
return <Navigate to='/login' replace />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Typically you would protect the route instead. If you wanted to do that:
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PrivateRoutes = () => {
const { user: currentUser } = useSelector((state) => state.auth);
return currentUser
? <Outlet />
: <Navigate to="/login" replace />;
}
...
<Routes>
<Route path='/' element={<Home />} />
<Route element={<PrivateRoutes />}>
<Route path='/create/upload' element={<Uploads />} />
... any other protected routes ...
</Route>
... other unprotected routes ...
</Routes>
I have a problem with one of my components in which react router returns a blank page when accessing it. It only happens in one component and I do not know why.The component that has the problem is the EditPost component.
App.js
This is where all the routes are.
import "./App.css";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from "./components/Home.js";
import Signup from "./components/signup/Signup";
import Login from "./components/Login/Login";
import Dashboard from "./components/Dashboard/Dashboard";
import PrivateRoute from "./components/common/PrivateRoutes";
import { Provider } from "react-redux";
import Store from "./Store";
import { Provider as AlertProvider } from "react-alert";
import AlertTemplate from "react-alert-template-basic";
import { loadUser } from "./actions/auth";
import { useEffect } from "react";
import PostPage from "./components/PostPage/PostPage";
import EditPost from "./components/EditPost/EditPost";
// Alert options
const alertOptions = {
timeout: 3000,
};
function App() {
useEffect(() => {
Store.dispatch(loadUser());
}, []);
return (
<div className="App">
<Provider store={Store}>
<AlertProvider template={AlertTemplate} {...alertOptions}>
<Router>
<Switch>
<Route exact path="/post-edit/:id" component={EditPost} />
<Route exact path="/signup" component={Signup} />
<Route exact path="/login" component={Login} />
<PrivateRoute exact path="/dashboard" component={Dashboard} />
<Route exact path="/" component={Home} />
<Route exact path="/post/:id" component={PostPage} />
</Switch>
</Router>
</AlertProvider>
</Provider>
</div>
);
}
export default App;
EditPost.js
This is the component that does not render
import React, { useState } from "react";
import { Form, Button } from "react-bootstrap";
import { connect } from "react-redux";
import { updatePost } from "../../actions/posts";
const EditPost = (props) => {
console.log(props);
const [title, setTitle] = useState(props.location.postData.title);
const [description, setDescription] = useState(
props.location.postData.description
);
const [body, setBody] = useState(props.location.postData.body);
const titleChange = (e) => {
setTitle(e.target.value);
};
const bodyChange = (e) => {
setBody(e.target.value);
};
const desChange = (e) => {
setDescription(e.target.value);
};
const addClick = (e) => {
e.preventDefault();
const post = { title, body, description };
props.updatePost(post);
};
return (
<div className="mt-4">
<h1>Edit Post</h1>
<Form>
<Form.Group>
<Form.Label>Post Title</Form.Label>
<Form.Control onChange={titleChange} type="text" />
</Form.Group>
<Form.Group>
<Form.Label>Short Description</Form.Label>
<Form.Control onChange={desChange} type="text" />
</Form.Group>
<Form.Group>
<Form.Label>Body</Form.Label>
<Form.Control onChange={bodyChange} as="textarea" rows={3} />
</Form.Group>
<Button onClick={addClick} variant="primary" type="submit">
Edit
</Button>
</Form>
</div>
);
};
const mapDispatchToProps = (dispatch) => {
return {
updatePost: (id, post) => dispatch(updatePost(id, post)),
};
};
export default connect(null, mapDispatchToProps)(EditPost);
Post.js
In this component is the link to the EditPost component.
import React from "react";
import { Card } from "react-bootstrap";
import dateFormat from "dateformat";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
const Post = (props) => {
return (
<Card>
<Card.Body>
<Link to={`/post/${props.id}`}>
<Card.Title>{props.title}</Card.Title>
</Link>
<Card.Text>{props.description}</Card.Text>
</Card.Body>
<Card.Footer>
<div className="d-flex justify-content-between">
<small className="text-muted">
{dateFormat(props.created, "mmmm dS, yyyy")}
</small>
{props.postUser === props.user ? (
<Link to={`/edit-post/${props.id}`}>
<i className="fas fa-edit" style={{ color: "grey" }}></i>
</Link>
) : null}
</div>
</Card.Footer>
</Card>
);
};
const mapStateToProps = (state) => {
if (state.auth.isAuthenticated) {
return {
user: state.auth.user.username,
};
} else {
return {};
}
};
export default connect(mapStateToProps)(Post);
Right now I am just trying to get the component rendered, then I will worry about the props. Thanks in advance.
Your link is /edit-post/${props.id}.
Your matched path is /post-edit/:id.
edit-post and post-edit are not the same thing 😉
I would recommend adding a NoMatch block at the end of your Switch. It'll help you to diagnose these problems faster.
<Route>
<NoMatch />
</Route>