I am building a "Forgot password" page. Following is the flow of my code:
A page with url /auth/forgot-password opens on clicking on Forgot Password.
It takes input as email and sends OTP to the registered email(if it exists in DB).
After sending OTP, it redirects to a new URL /auth/new-password.
Here, the remaining details are entered(ex. OTP,new password etc.)
Due to this flow, the user can access the path /auth/new-password by searching for it. But I don't want that to happen. User should only reach this url via /auth/forgot-password. User should be redirected to /auth/forgot-password URL if user searches for the prior one.
Currently in my Routes page I am doing this:
<ContentRoute path="/auth/forgot-password" component={ForgotPassword}/>
<ContentRoute path="/auth/new-password" component={NewPassword} />
Due to some restrictions I can't change the existing flow of the code.
How can I change this to exhibit the behavior explained above?
The easiest way is to create a HOC (Higher Order Component).
When I want a user to authenticate before accessing a sites' page, I always create a HOC called AuthRoute like this.
AuthRoute.js
import { connect } from "react-redux";
import { Redirect, Route } from "react-router-dom";
const AuthRoute = props => {
const { authUser, location } = props;
if(!authUser) {
return <Redirect to={{
pathname: "/",
state: { prevLoc: location.pathname }
}} />
}
return <Route {...props} />;
};
function mapStateToProps({ authUser }) {
return {
authUser
}
}
export default connect(mapStateToProps)(AuthRoute);
Then include that to the App component like this.
App.js
import { Fragment } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import AuthRoute from './components/AuthRoute'; // AuthRoute Import
import Dashboard from './components/Dashboard';
const App = () => {
return (
<Router>
<Switch>
{/* Include the AuthRoute component for the relevant page */}
<AuthRoute path="/home" component={Dashboard} />
<Route path="/" component={Login} />
</Switch>
</Router>
)
}
This implementation will check whether the user entered their email address on /auth/forgot-password page.
Completed project with HOC implementation - https://github.com/yushanwebdev/reactnd-would-you-rather
Related
I am new undergraduate interning for a startup(Appopener.com), I am trying to build a Login and Sign up page for their new website on reactjs. For this purpose I searched the internet for appropriate references, After spending a lot of time understanding the working of react-router-dom v5 , I was shocked to know that it had upgraded to v6 in which there were a lot of change made, for example - useHistory hook ----> became -----> useNavigate and so on...Now the part where I am stuck is the react-redux sessionService, I have used MongoDB and a simple backend API point which lets me login and signup, the users. Now the problem is when I try to set up a protected route, I am not able to generate a Authentication token from Reac-redux-session, I am trying to use the thunk library. All of the material on the internet seems to be outdated as they provide solution using react-router v5. I would obliged if anyone could help me with the code...
App.js File
// Pages
import Home from './pages/Home';
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import Dashboard from "./pages/Dashboard";
// styled components
import {StyledContainer} from './components/styles';
import Navbar from './components/Navbar';
import {BrowserRouter as Router, Routes , Route } from 'react-router-dom';
import Authroute from './components/Authroute';
function App() {
// Now using react router we need to setup routes for each component (genius extra ordinary mind fucking blown)
return (
<>
<Router>
<Navbar title="Spawnser" aboutText="Dashboard" />
<StyledContainer>
<Routes> {/*the switch will always check for the current link and choose which component to display */}
<Route element={<Authroute />}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
<Route path="/Signup" element={<Signup />} />
<Route path="/Login" element={<Login />} />
<Route path="/" element={ <Home />}></Route>
</Routes>
</StyledContainer>
</Router>
</>
);
}
export default App;
store.js file
// I know that createStore is depracted if thats the problem let me know....
import { legacy_createStore as createStore, applyMiddleware,compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/rootReducer';
import { sessionService } from 'redux-react-session';
const initialState = {};
const middlewares =[thunk];
const store = createStore(rootReducer, initialState, compose(applyMiddleware(...middlewares)));
sessionService.initSessionService(store);
export default store;
RootReducer.js
// The concept of the reducers is to interact with our store and change up values whenever needed.
import { combineReducers } from "redux";
//session
import { sessionReducer } from "redux-react-session";
const rootReducer = combineReducers({
session: sessionReducer
});
export default rootReducer;
Authroute.js
// With the Authroute I want to prevent user access to the Dashboard iff the user has NOT LOGGED IN...
import {Outlet, Navigate} from 'react-router-dom';
import {connect} from "react-redux";
//authenticated variable has been provided to us by redux react session , by this we know that user has been authenticated or not.
const AuthRoute = () => {
let auth = authenticated //---> This authenticated variable is the problem.
return (
auth ? <Outlet/> : <Navigate to ="/login"/>
)
// See the code below is the way you do it in react-router-v5, I tried to update it to react-router-v6 above but it give error when I try to use it in App.js (main file)
// import {Route, Redirect} from 'react-router-dom';
// import {connect} from 'react-redux';
// <Route {...rest}
// render={
// ({location})=> authenticated ? (children) : (
// <Redirect
// to={{
// pathname: "/login",
// state: {from: location}
// }}
// />
// )
// }
// />
const mapStatetoProps = ({session}) => {
authenticated: session.authenticated
}
export default connect(mapStatetoProps)(AuthRoute);
}
Now just like redux actions to be able to access authenticated from redux we need to pass it the connect function but for state variable such as authenticated we need to create mapstatetoprops function which retains an object and pass it it connect, hence we destructure the session or take the session from the redux state and fetch the authenticated value from it. I think the probem might lie here but THERE IS ABSOLUTELY NO CONTENT OVER HOW TO USE IT WITH THE UPDATE ANYWHERE...PLEASE HELP....
BasicRoute.js
// With the BasicRoute I want to ensure that once the user is logged in visiting the Signup or the Login page will redirect you to the Dashboard
// With the Authroute I want to prevent user access to the Dashboard iff the user has NOT LOGGED IN...
import {Outlet, Navigate} from 'react-router-dom';
import {connect} from "react-redux";
//authenticated has been provided to us by redux react session , by this we know that user has been authenticated or not.
const Basicroute = () => {
let auth = authenticated
return (
auth ? <Outlet/> : <Navigate to ="/login"/>
)
// See the code below is the way you do it in react-router-v5, I tried to update it to react-router-v6 above but it give error when i try to use it
// in App.js (main file)
// import {Route, Redirect} from 'react-router-dom';
// import {connect} from 'react-redux';
// <Route {...rest}
// render={
// ({location})=> !authenticated ? (children) : (
// <Redirect
// to={{
// pathname: "/dashboard",
// state: {from: location}
// }}
// />
// )
// }
// />
}
const mapStatetoProps = ({session}) => {
authenticated: session.authenticated
}
export default connect(mapStatetoProps)(Basicroute);
}
If you need code from any other FILE ALSO PLEASE LET ME KNOW....Thanks a ton
I have this route in my React app:
<Route path="/solution/:id/edit" element={user ? <SolutionEditForm /> : <Navigate to="/" />}/>
I would like to know how to secure this route, right now if a user is logged in they can access this route, but they can also access someone's solution edit route as well if they are logged in. This route should be accessible only by those who submitted the solution.
You could use a Private Route
// Private Route file
import { Navigate, useParams } from "react-router-dom";
const PrivateRoute = ({ user, children }) => {
const { id } = useParams();
// check for user first
if (!user) {
return <Navigate to="/" />;
}
// logic to check if `solution` id matches with user id
// and if not then navigate to "/"
return children;
};
export default PrivateRoute;
Now wrap your route with the above Private Route
<Route path="/solution/:id/edit" element={<PrivateRoute user={user}><SolutionEditForm /></PrivateRoute>}/>
I'm new to react, react-router-dom and redux.
I'm wondering if I can add an route that can't be acceses from url, only from application.
Use case is that I'm tryng to build registration form page which will on succesfull registration send an verification code to email and application will redirect to page to enter that verification code.
I was thinking of making new verification code page but that page can not be accesssed from typing it in url, it should be accesed only from application.
Is that possible or should I take another aproach?
In my opinion it's not a problem to have your "enter verification code" page be accessible from the URL. But if you want to do this then yes it can be done.
The render component for your verification page needs to check some condition. If that condition is met then it loads the page. If not, it renders a <Redirect/> to your form page.
I am using a property in location.state as that condition. When you navigate within the app then you will pass this property in you <Link/> component by using a to object instead of just a path string.
<Link to={{ pathname: "/verify", state: { fromApp: true } }}>Verify</Link>
The component for the verification page will use the useLocation hook to access the current location and see if the location state includes the fromApp property which we set.
const location = useLocation();
if (!location.state?.fromApp) {
return <Redirect to="/" />;
}
I created a very simple CodeSandbox demo.
You get redirected if you go to "/verify" directly. But you can access it just fine by clicking on the "Verify" link from the home page.
Complete Code:
import {
BrowserRouter,
Link,
Redirect,
Route,
Switch,
useLocation
} from "react-router-dom";
const Home = () => (
<div>
<h1>Home</h1>
<Link to={{ pathname: "/verify", state: { fromApp: true } }}>Verify</Link>
</div>
);
const VerificationPage = () => {
const { state } = useLocation();
if (!state?.fromApp) {
return <Redirect to="/" />;
}
return (
<div>
<h1>Enter Verification Code</h1>
<input />
<button>Submit</button>
</div>
);
};
export default function App() {
return (
<BrowserRouter>
<Switch>
<Route path="/verify" component={VerificationPage} />
<Route path="/" component={Home} />
</Switch>
</BrowserRouter>
);
}
I’m building a full stack react application. I have the stack operating and pulling information from the backend when I’m calling it from the frontend. I’m using axios on the frontend to hit the endpoints I’ve set up on the backend. I’m having an issue with frontend authentication though. I’m using passport and passport-google-oauth20 to log users in with Google OAuth2.0. I have this working correctly. The problem I’m having: when a user hits a specific URL (controlled by react-router-dom) I have a function called authVerify run that pings the backend and checks if the user is logged in (by looking at the cookie - not accessible in JS). The function runs correctly and correctly updates the state. I have a state field for authenticated originally set to false, and on a successful 200 response I setState for authenticated to true. But, the protected routes I’ve built aren’t working. If the user is authenticated, they should be able to go to that route. If not, they get redirected to the login page. But, everything just redirects to the login page.
Here is the Router.js code:
import React from 'react';
import axios from 'axios';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import Login from './Login';
import SignUp from './SignUp';
import App from './App';
import NotFound from './NotFound';
class Router extends React.Component {
constructor(props) {
super(props);
this.state = {
authenticated: false,
};
}
componentDidMount() {
this.authVerify()
.then((res) => {
if (res.status === 200) {
console.log(this.state);
this.setState({
authenticated: true,
});
console.log(this.state);
}
})
.catch(err => console.log(err));
}
authVerify = () => axios.get('/authVerify')
render() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/" component={App} />
<Route exact path="/pages/signup" component={SignUp} />
<Route exact path="/pages/login" component={Login} />
<PrivateRoute path="/pages/dashboard" authenticated={this.state.authenticated} component={App} />
<Route component={NotFound} />
</Switch>
</BrowserRouter>
);
}
}
export default Router;
And here is the PrivateRoute.js code:
import React from 'react';
import { Route, Redirect, withRouter } from 'react-router-dom';
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
<Route
{...rest}
render={props => (authenticated === true ? <Component {...props} /> : <Redirect to="/pages/login" />)}
/>
);
export default withRouter(PrivateRoute);
I have console logged the response from the API and I successfully get a 200 response. I’ve also console logged the state before and after the call to the API and before it’s false and after it’s true. But, I’m always redirected to the login page.
I think the issue is that the call to the API is taking too long and the component is mounting before authentication is verified. But, I thought by setting the state in the componentDidMount function would update the component and redirect appropriately. Any help would be appreciated.
I had the same problem when working with private routes, the issue is that your component is rendered before the resolution of your API call, the solution for me was to wait for the response to finish before rendering the component.
I used a isSendingRequest which starts as true and prevented my component from rendering (maybe you can display a spinner when this happens) after that when the request is finished, isSendingRequest is set to false and the component is rendered.
Hope it helps!
Please always add root path route "/" below of all other routes like:
<Route path="/checkout" component={YourComponent} />
.....
.....
<Route path="/" component={App} />
I have these scenarios
Settings Page -> Results Page -> Details Page
User chooses settings, clicks next, gets results and then clicks into more details.
Details Page -> Results Page
User goes back to Results page from Details Page. This causes a full re-render, causing me to hit the server again for no point(I have the results stored in an array).
Details Page -> Results Page -> Settings Page -> Results Page
The user goes to details, then back to results(want to grab stored results), then goes back to settings page, makes changes and then goes back to results page(now I want a full grab from server again).
I am wondering if there is away in react router to determine if I came to the page via the browser history or if I was going in a forward motion.
I was looking for the same thing then I finally find a solution that seems simple.
Quick answer:
I use history.action value to determine if the user is coming from a back button or from a classic navigation.
If history.action is equal to 'POP' then the back button has been hit. Otherwise it's 'PUSH'.
Detailed answer:
I get access to history in props of each page because I do something like that:
<Provider store = { store }>
<AppRouter />
</Provider>
Where AppRouter is:
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import PublicRoute from './PublicRoute';
import PageHome from '../pages/front/home/PageHome';
export const history = createHistory();
const AppRouter = () => (
<Router history={ history }>
<div>
<Switch>
<PublicRoute
exact = { true }
path = "/"
component = { PageHome }
history={ history }
/>
< ..... Other routes here ..... >
</Switch>
</div>
</Router>
);
export default AppRouter;
And PublicRoute component is something like that:
import React from 'react';
import { Route } from 'react-router-dom';
import Header from '../components/header/Header';
const PublicRoute = ( { component: Component, ...rest } ) => (
<Route
component={ ( props ) => (
<div>
<Header />
<Component { ...props } />
</div>
)}
{ ...rest }
/>
);
export default PublicRoute;
In React Router, the component stays mounted if router calls for paths that are children of that component. So, in your example, you can do something like the following:
<Route path="items" component={Results}>
<Route path=":id" component={Detail} />
</Route>
This way, Results component does not get unmounted when Detail component is being mounted because Detail is a child of Results. However, if you do not want to see Results component getting rendered when you are in Detail, you can only render children when they exist. Something like the following:
class Results extends React.Component {
render() {
if (this.props.children) {
// This will enter Detail component
return this.props.children;
}
return (
// Render results component
);
}
}