How to handle Authentication with react-router? - javascript

Trying to make certain routes require Authentication.
I have this:
class App extends Component {
render() {
const menuClass = `${this.props.contentMenuClass} col-xs-12 col-md-9`;
return (
<BrowserRouter history={browserHistory}>
<div className="App">
<Header properties={this.props} />
<div className="container-fluid">
<div className="row">
<SideNav />
<div className={menuClass} id="mainContent">
<Switch>
{routes.map(prop =>
(
<Route
path={prop.path}
component={prop.component}
key={prop.id}
render={() => (
!AuthenticationService.IsAutheenticated() ?
<Redirect to="/Login"/>
:
<Route path={prop.path}
component={prop.component}
key={prop.id}/>
)}
/>
))}
</Switch>
</div>
</div>
</div>
{/* <Footer /> */}
</div>
</BrowserRouter>
);
}
}
const mapStateToProps = state => ({
contentMenuClass: state.menu,
});
export default connect(mapStateToProps)(App);
Note: Yes the auth service works as it should.
For every route I am checking if the user is authenticated, if not then I want to redirect them to the login page, if they are then it will land on the first page with the route of "/".
All I am getting is:
react-dom.development.js:14227 The above error occurred in the <Route> component:
in Route (created by App)
in Switch (created by App)
in div (created by App)
in div (created by App)
in div (created by App)
in div (created by App)
in Router (created by BrowserRouter)
in BrowserRouter (created by App)
in App (created by Connect(App))
in Connect(App)
in Provider
Where am I doing this wrong?

A simple solution would be to make a HOC (High Order Component) that wraps all protected routes.
Depending upon how nested your app is, you may want to utilize local state or redux state.
Working example: https://codesandbox.io/s/5m2690nn6n (this uses local state)
routes/index.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Home from "../components/Home";
import Players from "../components/Players";
import Schedule from "../components/Schedule";
import RequireAuth from "../components/RequireAuth";
export default () => (
<BrowserRouter>
<RequireAuth>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/players" component={Players} />
<Route path="/schedule" component={Schedule} />
</Switch>
</RequireAuth>
</BrowserRouter>
);
components/RequireAuth.js
import React, { Component, Fragment } from "react";
import { withRouter } from "react-router-dom";
import Login from "./Login";
import Header from "./Header";
class RequireAuth extends Component {
state = { isAuthenticated: false };
componentDidMount = () => {
if (!this.state.isAuthenticated) {
this.props.history.push("/");
}
};
componentDidUpdate = (prevProps, prevState) => {
if (
this.props.location.pathname !== prevProps.location.pathname &&
!this.state.isAuthenticated
) {
this.props.history.push("/");
}
};
isAuthed = () => this.setState({ isAuthenticated: true });
unAuth = () => this.setState({ isAuthenticated: false });
render = () =>
!this.state.isAuthenticated ? (
<Login isAuthed={this.isAuthed} />
) : (
<Fragment>
<Header unAuth={this.unAuth} />
{this.props.children}
</Fragment>
);
}
export default withRouter(RequireAuth);
Or, instead of wrapping routes, you can create a protected component that houses protected routes.
Working example: https://codesandbox.io/s/yqo75n896x (uses redux instead of local state).
routes/index.js
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import Home from "../components/Home";
import Header from "../containers/Header";
import Info from "../components/Info";
import Sponsors from "../components/Sponsors";
import Signin from "../containers/Signin";
import RequireAuth from "../containers/RequireAuth";
import rootReducer from "../reducers";
const store = createStore(rootReducer);
export default () => (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/info" component={Info} />
<Route path="/sponsors" component={Sponsors} />
<Route path="/protected" component={RequireAuth} />
<Route path="/signin" component={Signin} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
containers/RequireAuth.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import ShowPlayerRoster from "../components/ShowPlayerRoster";
import ShowPlayerStats from "../components/ShowPlayerStats";
import Schedule from "../components/Schedule";
const RequireAuth = ({ match: { path }, isAuthenticated }) =>
!isAuthenticated ? (
<Redirect to="/signin" />
) : (
<div>
<Route exact path={`${path}/roster`} component={ShowPlayerRoster} />
<Route path={`${path}/roster/:id`} component={ShowPlayerStats} />
<Route path={`${path}/schedule`} component={Schedule} />
</div>
);
export default connect(state => ({
isAuthenticated: state.auth.isAuthenticated
}))(RequireAuth);
You can even get more modular by creating a wrapper function. You would be able to pick and choose any route by simply wrapping over the component. I don't have a codebox example, but it would be similar to this setup.
For example: <Route path="/blog" component={RequireAuth(Blog)} />

You spelled Autheenticated wrong.
Also, this is an assumption because you only provided the stack trace and not the above error, which probably says AuthenticationService.IsAutheenticated is not a function.

Related

Question about PrivateRoutes and to get navigated back to login page

Hi I need help getting my code to work so that when I try to log back in I won't be able to view the dashboard since I logged out. Right now its giving me a blank screen in my project and I think its because privateroute isn't a thing anymore? Not sure. This is my code in PrivateRoute.js:
import React, { Component } from 'react';
import { Route, Navigate } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
const PrivateRoute = ({ component: Component, auth: { isAuthenticated, loading },
...rest }) => (
<Route {...rest} render={props => !isAuthenticated && !loading ? (<Navigate to='/login' />) : (<Component {...props} />)} />
)
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStatetoProps = state => ({
auth: state.auth
});
export default connect(mapStatetoProps)(PrivateRoute);
This is the code for the app.js:
import React, { Fragment, useEffect } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Navbar from './components/layout/Navbar';
import Landing from './components/layout/Landing';
import Register from './components/auth/Register';
import Login from './components/auth/Login';
import Alert from './components/layout/Alert';
import Dashboard from './components/dashboard/Dashboard';
import PrivateRoute from './components/routing/PrivateRoute';
// Redux
import { Provider } from 'react-redux';
import store from './store';
import { loadUser } from './actions/auth';
import setAuthToken from './utils/setAuthToken';
import './App.css';
if (localStorage.token) {
setAuthToken(localStorage.token);
}
const App = () => {
useEffect(() => {
store.dispatch(loadUser());
}, []);
return (
<Provider store={store}>
<Router>
<Fragment>
<Navbar />
<Routes>
<Route exact path='/' element={<Landing/>} />
</Routes>
<section className="container">
<Alert />
<Routes>
<Route exact path='/Register' element={<Register/>} />
<Route exact path='/Login' element={<Login/>} />
<PrivateRoute exact path='/dashboard' element={Dashboard} />
</Routes>
</section>
</Fragment>
</Router>
</Provider>
)};
export default App;
Please Help!
there is a recommended Protected Route implementation in the v6 react router docs you can follow.
DOCS IMPLEMENTATION
Your issue:
<Route {...rest} render={props => !isAuthenticated && !loading ?
(<Navigate to='/login' />) : (<Component {...props} />)} />
This Route is not up to date with v6, which I assume you are using, you need to update it.
Exact is also not supported anymore, there is no point in including it in your routes.
If you are not using v6 please update your post and include your react-router version.

React conditional rendering not triggering?

I am trying to change the contents of my Navbar and my Router via useContext and conditional rendering.
This is my App.js:
import "./App.css";
import axios from "axios";
import { AuthContextProvider } from "./context/AuthContext";
import MyRouter from "./MyRouter";
axios.defaults.withCredentials = true;
function App() {
return (
<AuthContextProvider>
<MyRouter />
</AuthContextProvider>
);
}
export default App;
This is my Router:
import React, { useContext } from "react";
import AuthContext from "./context/AuthContext";
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
} from "react-router-dom";
import MyNavBar from "./components/MyNavbar";
import "./App.css";
import Home from "./components/pages/Home";
import AboutUs from "./components/pages/AboutUs";
import Register from "./components/pages/Register";
import MyFooter from "./components/MyFooter";
import login from "./components/pages/login";
import ProfilePage from "./components/pages/ProfilePage";
function MyRouter() {
const { loggedIn } = useContext(AuthContext);
return (
<Router>
<MyNavBar />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/about-us" component={AboutUs} />
{loggedIn === false && (
<>
<Route exact path="/register" component={Register} />
<Route exact path="/login" component={login} />
</>
)}
{loggedIn === true && (
<>
<Route exact path="/profile" component={ProfilePage} />
</>
)}
<Redirect to="404" />
</Switch>
<MyFooter />
</Router>
);
}
export default MyRouter;
My Navbar's conditional rendering works in the same way as the router. My problem is that neither of the conditional rendering fragments are working. For example, when my application starts, users are not logged in and the "loggedIn" value is false. With this logic, the routes "register" and "login" should be accessible, but they are not. Any advice or help would be greatly appreciated as I am quite new to using React.
Here is a screenshot of my console upon loading the application
This is my "AuthContext":
const AuthContext = createContext();
function AuthContextProvider(props) {
const [loggedIn, setLoggedIn] = useState(undefined);
async function getLoggedIn() {
const loggedInRes = await axios.get(
"http://localhost:5000/api/users/loggedIn"
);
setLoggedIn(loggedInRes.data);
}
useEffect(() => {
getLoggedIn();
}, []);
return (
<AuthContext.Provider value={{ loggedIn, getLoggedIn }}>
{props.children}
</AuthContext.Provider>
);
}
export default AuthContext;
export { AuthContextProvider };
Thanks for all the help guys, I learned a lot about the ProtectedRoute concept. My issue came from my Navigation Bar component. Instead of declaring my variable as an object, I declared loggedin like
const loggedIn = useContext(AuthContext);
All my errors were solved once I changed it to
const { loggedIn } = useContext(AuthContext);
Edited:
Instead of using your conditions in your main route, you can create a ProtectedRoute which Routes your component according to your condition.
import React, { useEffect } from "react";
import { Route, Redirect, useHistory } from "react-router-dom";
const ProtectedRoute = ({ component: Component, ...rest }) => {
const { loggedIn } = useContext(AuthContext);
return (
<Route
{...rest}
render={(props) =>
loggedIn() ? (
<Component {...props} />
) : (
<Redirect to='/login' />
)
}
/>
);
};
export default ProtectedRoute;
In your component you can change your logic with this:
<ProtectedRoute exact path="/" component={Home} />
<ProtectedRoute exact path="/about-us" component={AboutUs}/>
<Route exact path="/register" component={Register} />
<Route exact path="/login" component={login} />
<ProtectedRoute exact path="/profile" component={ProfilePage} />
This means that if you wanna protect a route you should route it with ProtectedRoute otherwise classic react route.
You can read about that ProtectedRoute concept here

Component is not Getting Rendered - Dynamic Routing React

I can get the data of the players when i Route to the Players Component,
but when i click on the Link Tags, the PLayersContainer Component is not opening.
This is my App.js File.
import React, { Component } from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import Players from './Components/Players'
import PlayersContainer from './Components/Container/playersContainer'
import Navigation from './Components/Navigation';
export default class App extends Component {
state = {
players:[
{
id:1,
name:'Ronaldo'
},
{
id:2,
name:'Messi'
}
]
}
render() {
return (
<Router>
<Navigation />
<Switch>
<Route path="/players" render={(props) => <Players {...props} players={this.state.players} />} />
<Route exact path="/players/:id" render={PlayersContainer} />
</Switch>
</Router>
)
}
}
This is my Players Component.
import React from 'react'
import { Link } from 'react-router-dom'
export default function Players(props) {
const renderPlayers = () => {
let players = props.players.map(playerObj => <li> <Link to={`/players/${playerObj.id}`}> Player {playerObj.name} </Link></li>)
return players
}
return (
<div>
<ul>
{renderPlayers()}
</ul>
</div>
)
}
This is my PlayersContainer Component, where i want to render the individual data of the Player.
import React from 'react'
import { Link } from 'react-router-dom'
export default function PlayersContainer(props) {
const renderPlayers = () => {
console.log(props);
}
return (
<div>
<ul>
{renderPlayers()}
</ul>
</div>
)
}
You have the wrong Route marked as exact. The way its written currently, anything beginning with /players will match the first route. Since its in a switch, only the first match will be rendered.
Change it from:
<Route path="/players" render={(props) => <Players {...props} players={this.state.players} />} />
<Route exact path="/players/:id" render={PlayersContainer} />
to this:
<Route exact path="/players" render={(props) => <Players {...props} players={this.state.players} />} />
<Route path="/players/:id" render={PlayersContainer} />
Now only exactly /players will match the first route, and /players/id can continue past it to match the second.

React Where to place login page for a one page app with a side menu

I have a react web app with a sidemenu. Whenever a user clicks on the link in the sidemenu, they are routed to a page that is rendered at the right side of the sidemenu. My question is, how do I do login for such a usecase seeing as any page I route to renders to the right of the sidemenu. I want the login page to be full screen without the side menu showing. This is what App.js looks like.
import React, { Component } from "react";
import { HashRouter } from "react-router-dom";
import Navigation from "./pages/General/components/Navigation";
import SideMenu from "./pages/General/components/SideMenu";
import "../src/css/App.css";
class App extends Component {
render() {
return (
<div>
<HashRouter>
<div className="main-wrapper">
<SideMenu />
<Navigation />
</div>
</HashRouter>
</div>
);
}
}
export default App;
Here is Navigation.js
import React from "react";
import { Route } from "react-router-dom";
import CalendarPage from "../../Calendar/CalendarPage";
import DoctorsList from "../../Doctors/DoctorsList";
import PatientsList from "../../Patients/PatientsList";
import AdminUsersList from "../../AdminUsers/AdminUsersList";
import SpecialitiesList from "../../Specialities/SpecialitiesList";
const Navigation = () => {
return (
<div className="mainarea">
<Route exact path="/" component={CalendarPage} />
<Route exact path="/scheduler" component={CalendarPage} />
<Route exact path="/doctors" component={DoctorsList} />
<Route exact path="/patients" component={PatientsList} />
<Route exact path="/admin-users" component={AdminUsersList} />
<Route exact path="/specialities" component={SpecialitiesList} />
</div>
);
};
export default Navigation;
The best solution I can figure out in terms of a clean design, is to implement another router in your App.jsx, because you are implementing the routing inside your component, and you need another one for your login page.
Then, your App.jsx could be like this:
import React, { Component } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import LogIn from "./pages/General/components/Login";
import HomePage from "./pages/General/components/HomePage";
import "../src/css/App.css";
class App extends Component {
render() {
return (
<div>
<Switch>
<Route path={'/login'} component={LogIn} />
<Route path={'/'} component={HomePage} />
<Redirect to="/" />
</Switch>
</div>
);
}
}
export default App;
Then, for your HomePage do the following
import React, { Component } from "react";
import { HashRouter } from "react-router-dom";
import Navigation from "./pages/General/components/Navigation";
import SideMenu from "./pages/General/components/SideMenu";
import "../src/css/App.css";
class HomePage extends Component {
render() {
return (
<div>
<HashRouter>
<div className="main-wrapper">
<SideMenu />
<Navigation />
</div>
</HashRouter>
</div>
);
}
}
export default HomePage;
I hope it helps!
Here is my solution, it not exactly a solution, but it will give you a basic idea on how to implement this.
The idea is to place the Login component in app.js, and conditionally display it if the user is logged in.
You will have to pass a handler function to login component through which you will be able to control app.js state.
When login will be sucessfull, u can show the Navigation and Sidemenu component.
import { Fragment } from "react";
import Login from "path/to/login";
class App extends Component {
state = { isLoggedIn: false };
loginHandler = () => {
this.setState({
isLoggedIn: true
});
};
render() {
return (
<div>
<div className="main-wrapper">
{isLoggedIn ? (
<Fragment>
<SideMenu />
<Navigation />
</Fragment>
) : (
<Login loginHandler={this.loginHandler} />
)}
</div>
</div>
);
}
}
Also you need write a separate router file, which will contain the main app.
This is used to show the app component when navigated to /
import React from 'react';
import { HashRouter, Route } from 'react-router-dom';
import App from './app';
const MainRoute = () => (
<HashRouter>
<Route path="/" component={App} />
</HashRouter>
);
export default MainRoute;

Child component not rendering with React Router v4

I am trying my hands on React Nested routing & this is my how my app looks like
Posts.js (Parent (plural) Component) which is rendering fine.
import React from "react";
import { Route } from "react-router-dom";
import Post from "./Post";
import { Link, Switch } from "react-router-dom";
const Posts = ({ match }) => {
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${match.url}/components`}>Components</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
<div>
<Route path={`${match.path}/:topicId`} component={Post} />
<Route
exact
path={match.path}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
</div>
)}
export default Posts;
Post Component (Child (Singular) Component)
import React from "react";
const Post = ({ match }) => (
<div>
<h1>Child component</h1>
<h3>{match.params.topicId}</h3>
</div>
);
export default Post;
Not sure what config is lacking here, the parent component is rendering fine on the route while the child component content is not rendering
No error in console.
Parent Routing Configuration
import React from "react";
import { Switch, Route,NavLink } from "react-router-dom";
import Home from "./container/home/Home";
import About from "./container/about/About";
import Posts from "./container/post/posts";
import PageNotFound from "./container/Error/404";
const routes = () => (
<Switch>
<Route path="/" exact component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route exact path="/post" component={Posts}></Route>
<Route component={PageNotFound}> </Route>*/}
</Switch>
)
export default routes;
App.js
import React, { Component } from 'react';
import './App.css';
import Layout from "./hoc/layout/layout";
class App extends Component {
render() {
return (
<Layout></Layout>
);
}}
export default App;
So the mistake in your code is pretty trivial. In the parent Route, i.e the Route that is rendering Posts component, you have an exact keyword and hence Routes like /posts/:postId won't match this exact Route with path /post and hence the inner Routes or nested Child Routes won't work
You need to change your Routes config like
const routes = () => (
<Switch>
<Route path="/" exact component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route path="/post" component={Posts}></Route>
<Route component={PageNotFound}> </Route>*/}
</Switch>
)
export default routes;

Categories

Resources