React-Redux-session , authentication issue - reactjs

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

Related

Conditional Routes in react router dom v6

I am making a mern application with login/registration system and the dashboard and the other routes that will only be accessible when someone logs in now the problem is i was trying to write a if condition in react router dom so that all the routes inside the dashboard could be resitricted till the user logs in , and i use 'useNavigate' hook to go back to login page if user is not logged in but the application gives me error saying useNavigate() may be used only in the context of a component , but i used the same hook in my other component too where i didnt used any router and it worked fine there and so i am not able to understand what to do , also i want to know how can i put the component name inside a variable so that if i call the function i can put the component name in it and later put that varibale like this <component_name/> and it should change its value , here is my code:-
import Navbar from "./components/navbar/Navbar";
import Home from "./components/Home/Home";
import 'bootstrap/dist/css/bootstrap.min.css'
import Forgot_password from "./components/login_and_reg/Forgot_password/Forgot_password";
import Weather from "./components/Weather/Weather";
import Landing_page from './components/login_and_reg/Landing_page/Landing_page'
import {
BrowserRouter as Router,
Route,
Routes,
useNavigate
} from "react-router-dom";
import Verification from "./components/login_and_reg/Verification/Verification";
import Protected_routes from './components/Protected_routes'
import { useSelector } from "react-redux";
function App() {
const loggedin = useSelector(state => state.loggedin)
const Navigate = useNavigate();
const rememberMe = localStorage.getItem('rememberMe')
function checkroute(Component_name){
if (rememberMe=='true') {
return <Component_name/>
} else {
console.log(loggedin)
if(loggedin =='loggedin'){
return <Component_name/>
}
else{
Navigate('/')
}
}
}
return (
<>
<Router>
{/* <Navbar/> */}
<Routes>
<Route path="/weather" element={checkroute(Weather)}></Route>
<Route exact path="/" element={<Protected_routes/>}></Route>
<Route path="/verify/:first_name/:email" element={<Verification/>}></Route>
<Route path="/forgot_password" element={<Forgot_password/>}></Route>
{/* <Route exact path="/home" element={<Protected_routes/>}></Route> */}
</Routes>
</Router>
</>
);
}
I also made a protected route only for login purpose but i dont know how to use it for all the components if it is possible then here is code of that component:-
import React, { Component } from 'react';
import { useSelector } from 'react-redux';
import {Navigate, Route , useNavigate} from 'react-router-dom';
import Home from './Home/Home';
import Landing_page from './login_and_reg/Landing_page/Landing_page';
const Protected_routes = () => {
const loggedin = useSelector(state => state.loggedin)
const Navigate = useNavigate();
const rememberMe = localStorage.getItem('rememberMe')
if (rememberMe=='true') {
return <Home/>
} else {
if(loggedin=='loggedin'){
return <Home/>
}
else{
return <Landing_page/>
}
}
}
export default Protected_routes
export default App;
What you'd likely use a PrivateRoute component to wrap your secured pages. It will render the desired page if not logged in, or redirect to the login page.
Here's the flow:
1. Define a private route component:
// PrivateRoute.ts
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
import React from 'react';
export function PrivateRoute({ children }: { children: React.ReactElement }) {
// the user verification logic is up to your application
// this is an example based on your code above
const loggedin = useSelector(state => state.loggedin);
const rememberMe = localStorage.getItem('rememberMe');
if (rememberMe==='true') {
return <Navigate to={'/home'} />;
}
else if (loggedin==='loggedin'){
// render the wrapped page
return children;
}
else {
// user not logged in, redirect to the Login page which is unprotected
return <Navigate to={'/login'} />;
}
}
2. And use it in your router :
I assume you want to protect your Home and Wheater pages here. You can customize it to your own logic.
import React from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { PrivateRoute } from './PrivateRoute';
import { Home } from "./my-home-page";
import { Wheater } from "my-wheater-page";
import { Login } from "my-login-page";
export const AppRoutes = () => {
return (
<Routes>
<Route
path='home'
element={
<PrivateRoute>
<Home/>
</PrivateRoute>
}
/>
<Route
path='login'
element={
<Login/>
}
/>
<Route
path='wheater'
element={
<PrivateRoute>
<Wheater />
</PrivateRoute>
}
/>
</Routes>
);
};

Conditional Redirection to a URL in react

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

store.getState is always returning the default state

I have a react app with Oauth handled on the server by express and passport. I want to configure authentication based routing i.e. only certain routes must to available to browse without logging in otherwise navigating to those routes must redirect the user to the Login page.
So initially when the app loads(App.js), in the componentDidMount method I have called an API which fetches the user. If the user is returned it means he is logged in else he is logged out. Im using redux so this user gets stored in the redux store after an action is dispatched once the API is called.
I have an auth.js file where I am retrieving the state of the user, but it is always null and because of that its returning false. Why is this happening?
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose} from 'redux';
import reducers from './reducers/index';
import thunk from 'redux-thunk';
const store = createStore(reducers, {}, compose(applyMiddleware(thunk),window.devToolsExtension && window.devToolsExtension() ));
export default store;
ReactDOM.render(<Provider store={store}><App/></Provider>, document.getElementById('root'));
App.js
import React from 'react';
import Login from './Login';
import Dashboard from './Dashboard';
import { BrowserRouter, Route} from 'react-router-dom';
import {connect} from 'react-redux';
import * as actions from '../actions/userActions';
import RouteAuthenticator from '../components/routes/routeAuthenticator';
class App extends React.Component {
componentDidMount () {
this.props.fetchUser();
}
render() {
return (
<div>
<BrowserRouter>
<Route exact path="/" component={Login}/>
<RouteAuthenticator path="/home/dashboard" component={Dashboard}/>
</BrowserRouter>
</div>
)
}
}
export default connect(null, actions)(App);
RouteAuthenticator.js
import React from 'react';
import {Route, Redirect} from 'react-router-dom';
import isLoggedIn from '../../services/auth';
const RouteAuthenticator = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
isLoggedIn()
? <Component {...props} />
: <Redirect to='/' />
)} />
)
export default RouteAuthenticator;
auth.js
import store from '../index';
export default function isUserLoggedIn () {
const state = store.getState();
if (state.user) {
return true;
}
return false;
};

type error cannot read property 'push' of undefined reactjs [duplicate]

I am developing an application in which I check if the user is not loggedIn. I have to display the login form, else dispatch an action that would change the route and load other component. Here is my code:
render() {
if (isLoggedIn) {
// dispatch an action to change the route
}
// return login component
<Login />
}
How can I achieve this as I cannot change states inside the render function.
Considering you are using react-router v4
Use your component with withRouter and use history.push from props to change the route. You need to make use of withRouter only when your component is not receiving the Router props, this may happen in cases when your component is a nested child of a component rendered by the Router and you haven't passed the Router props to it or when the component is not linked to the Router at all and is rendered as a separate component from the Routes.
import {withRouter} from 'react-router';
class App extends React.Component {
...
componenDidMount() {
// get isLoggedIn from localStorage or API call
if (isLoggedIn) {
// dispatch an action to change the route
this.props.history.push('/home');
}
}
render() {
// return login component
return <Login />
}
}
export default withRouter(App);
Important Note
If you are using withRouter to prevent updates from being blocked by
shouldComponentUpdate, it is important that withRouter wraps the
component that implements shouldComponentUpdate. For example, when
using Redux:
// This gets around shouldComponentUpdate
withRouter(connect(...)(MyComponent))
// This does not
connect(...)(withRouter(MyComponent))
or you could use Redirect
import {withRouter} from 'react-router';
class App extends React.Component {
...
render() {
if(isLoggedIn) {
return <Redirect to="/home"/>
}
// return login component
return <Login />
}
}
With react-router v2 or react-router v3, you can make use of context to dynamically change the route like
class App extends React.Component {
...
render() {
if (isLoggedIn) {
// dispatch an action to change the route
this.context.router.push('/home');
}
// return login component
return <Login />
}
}
App.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default App;
or use
import { browserHistory } from 'react-router';
browserHistory.push('/some/path');
In react-router version 4:
import React from 'react'
import { BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
const Example = () => (
if (isLoggedIn) {
<OtherComponent />
} else {
<Router>
<Redirect push to="/login" />
<Route path="/login" component={Login}/>
</Router>
}
)
const Login = () => (
<h1>Form Components</h1>
...
)
export default Example;
Another alternative is to handle this using Thunk-style asynchronous actions (which are safe/allowed to have side-effects).
If you use Thunk, you can inject the same history object into both your <Router> component and Thunk actions using thunk.withExtraArgument, like this:
import React from 'react'
import { BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
import { createBrowserHistory } from "history"
import { applyMiddleware, createStore } from "redux"
import thunk from "redux-thunk"
const history = createBrowserHistory()
const middlewares = applyMiddleware(thunk.withExtraArgument({history}))
const store = createStore(appReducer, middlewares)
render(
<Provider store={store}
<Router history={history}>
<Route path="*" component={CatchAll} />
</Router
</Provider>,
appDiv)
Then in your action-creators, you will have a history instance that is safe to use with ReactRouter, so you can just trigger a regular Redux event if you're not logged in:
// meanwhile... in action-creators.js
export const notLoggedIn = () => {
return (dispatch, getState, {history}) => {
history.push(`/login`)
}
}
Another advantage of this is that the url is easier to handle, now, so we can put redirect info on the query string, etc.
You can try still doing this check in your Render methods, but if it causes problems, you might consider doing it in componentDidMount, or elsewhere in the lifecycle (although also I understand the desire to stick with Stateless Functional Compeonents!)
You can still use Redux and mapDispatchToProps to inject the action creator into your comptonent, so your component is still only loosely connected to Redux.
This is my handle loggedIn. react-router v4
PrivateRoute is allow enter path if user is loggedIn and save the token to localStorge
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props => (localStorage.token) ? <Component {...props} /> : (
<Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>
)
}
/>
);
}
Define all paths in your app in here
export default (
<main>
<Switch>
<Route exact path="/signin" component={SignIn} />
<Route exact path="/signup" component={SignUp} />
<PrivateRoute path="/" component={Home} />
</Switch>
</main>
);
Those who are facing issues in implementing this on react-router v4. Here is a working solution for navigating through the react app programmatically.
history.js
import createHistory from 'history/createBrowserHistory'
export default createHistory()
App.js OR Route.jsx. Pass history as a prop to your Router.
import { Router, Route } from 'react-router-dom'
import history from './history'
...
<Router history={history}>
<Route path="/test" component={Test}/>
</Router>
You can use push() to navigate.
import history from './history'
...
render() {
if (isLoggedIn) {
history.push('/test') // this should change the url and re-render Test component
}
// return login component
<Login />
}
All thanks to this comment: https://github.com/ReactTraining/react-router/issues/3498#issuecomment-301057248
render(){
return (
<div>
{ this.props.redirect ? <Redirect to="/" /> :'' }
<div>
add here component codes
</div>
</div>
);
}
I would suggest you to use connected-react-router https://github.com/supasate/connected-react-router
which helps to perform navigation even from reducers/actions if you want.
it is well documented and easy to configure
I was able to use history within stateless functional component, using withRouter following way (needed to ignore typescript warning):
import { withRouter } from 'react-router-dom';
...
type Props = { myProp: boolean };
// #ts-ignore
export const MyComponent: FC<Props> = withRouter(({ myProp, history }) => {
...
})
import { useNavigate } from "react-router-dom"; //with v6
export default function Component() {
const navigate = useNavigate();
navigate.push('/path');
}
I had this issue and just solved it with the new useNavigate hook in version 6 of react-router-dom

React Router v4 redirect to authenticated route when user successfully signIn

I am newbie for react and migrated from angular and trying to implement a simple JWT authenticated Flux react app with Route v4. I have successfully send request to Rails API and got my JWT token. Now I couldn't find any programmatically logical way to redirect my app to the authenticated Route component in LoginAction.
index.js
import React from 'react';
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import {createBrowserHistory} from 'history';
import Layout from './components/Layout';
import Login from './components/Login';
import Signup from './components/Signup';
import registerServiceWorker from './registerServiceWorker';
import AuthenticatedApp from './components/AuthenticatedApp';
const app = document.getElementById('root');
ReactDOM.render(
<Router history={createBrowserHistory}>
<div>
<Route exact path="/" component={Layout} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<Route path="/record" component={AuthenticatedApp} />
</div>
</Router>,
app);
registerServiceWorker();
LoginAction.js
In my LoginAction after a successful request to api with valid token I want to redirect the page to AuthenticatedApp component. Since there was only reference to older version of routes however Route 4 version have very few support in internet.
import AppDispatcher from '../dispatchers/AppDispatcher.js';
import LOGIN_USER from '../constants/LoginConstants';
import LOGOUT_USER from '../constants/LoginConstants';
import RouterContainer from '../services/RouterContainer'
export default {
loginUser: (jwt) => {
var savedJwt = localStorage.getItem('jwt');
AppDispatcher.dispatch({
actionType:LOGIN_USER,
jwt: jwt
});
if (savedJwt !== jwt) {
{want to redirect to '/record' of AuthenticatedAPP component}
localStorage.setItem('jwt', jwt);
}
},
logoutUser: () => {
RouterContainer.get().transitionEnter('/home');
localStorage.removeItem('jwt');
AppDispatcher.dispatch({
actionType:LOGOUT_USER
});
}
}
Finally, I have not still found what would be the replacement for these getCurrentQuery(), transitionTo in Route v4.
React Router v4 has a Redirect component. If there is a valid jwt, you could redirect to the authenticated part of the app. Something like
// drop this in your render()
const path = yourIsLoggedInCheck ? '/loggedInHome' : '/login';
<Redirect to={path} />;
Just make sure you're still doing auth checks in the logged in part of your app.
Finally, I have not still found what would be the replacement for
these getCurrentQuery()
You can use the withRouter higher order component to grab your query string data. Wrapping your component will give you access to a location prop.
// let's say you're looking for a query string param called myParam
// ...
let myParam = null;
if (this.props.location.search) {
myParam = location.search.split('myParam=')[1].split('&')[0] || null;
}
// ...
export default withRouter(YourComponent);

Resources