I have a react app where on loading the App component first the app checks whether there is token in localstorage, If it is there then it takes the token and verifies it, If the token is valid then the user is taken to dashboard and if it isn't the user is taken to login page, But however what is happening is that the page is getting continuosly refreshed, I have used window.location.href to redirect the user to other page, Am I doing something wrong please let me know I have posted the code below
import React, { Component } from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import setAuthToken from "./utils/setAuthToken";
import jwtDecode from "jwt-decode";
import store from "./store/store";
import Dashboard from "./components/app/Dashboard";
import HomePage from "./components/homepage/HomePage";
import Navbar from "./components/common/Navbar";
import { UserDetails } from "./components/common/UserDetails";
import { Provider } from "react-redux";
import { logoutUser } from "./store/actions/authActions";
import { setCurrentUser } from "./store/actions/usersActions";
class App extends Component {
componentDidMount() {
if (localStorage.getItem("user")) {
const token = localStorage.getItem("user");
console.log("token available");
const decoded = jwtDecode(token);
var currentTime = Date.now().toString();
currentTime = currentTime.substring(0, currentTime.length - 3);
const currentTimeNum = parseInt(currentTime);
if (currentTimeNum > decoded.exp) {
console.log("logged out");
setAuthToken();
store.dispatch(logoutUser());
window.location.href = "/home-page";
} else {
console.log("logged in");
setAuthToken(token);
window.location.href = "/";
store.dispatch(setCurrentUser());
}
} else {
console.log("logged out");
setAuthToken();
window.location.href = "/home-page";
}
}
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div className="App">
<Navbar />
<Switch>
<Route path="/home-page" component={HomePage} exact />
<Route path="/" component={Dashboard} exact />
<Route path="/user/:username" component={UserDetails} exact />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
}
}
export default App;
remove the BrowserRouter from App.js and put it above app by doing this in index.js ->
<BrowserRouter> <App> <BrowserRouter/>
and replace this
window.location.href = "/foo-bar"
with this
this.props.history.push("/foo-bar")
or
this.props.history.replace("/foo-bar")
In your code, if the token is valid, you redirect the user to “/“ performing a browser refresh. This will trigger the entire webapp to reload and so App component componentDidMount to be called, the token will be valid again and the page will refresh again to "/".
Instead of using location.href you should use the history object:
import { createBrowserHistory } from 'history';
import { Router } from 'react-router';
const history = createBrowserHistory();
class App extends Component {
componentDidMount() {
// instead of window.location.href = 'some-url'
history.push('/some-url');
}
//..
render() {
return (
<Provider store={store}>
<Router history={history}>
{/* ... */}
</Router>
</Provider>
)
}
}
Related
I have two(USER and ADMIN) users in my react project and I want to redirect them to their respected page after a successful login. To achieve this I created a standalone component called authenticatedRoute.js and wrap those components by AuthenticatedRoute component. Here are the codes
AuthenticatedRoute.js
import { useHistory } from "react-router-dom";
import { Route } from "react-router-dom";
const AuthenticatedRoute = ({ children, ...rest }) => {
const history = useHistory();
var tokenn = JSON.parse(localStorage.getItem("desta"));
var user_type = tokenn?.user.roles[0]; //USER or ADMIN
var loggedIn = tokenn?.user_status; //true
const redirect = () => {
try {
switch (user_type) {
case "USER":
return [history.push("/user"), children];
case "ADMIN":
return [history.push("/admin"), children];
default:
console.log("redirect");
}
} catch (err) {
return [history.push("/signin"), children];
}
};
return (
<Route
render={() => {
return loggedIn ? redirect() : history.push("/signin");
}}
/>
);
};
export default AuthenticatedRoute;
App.js (minimal code)
import "./App.css";
import Header from "./components/Header/Header";
import HeaderLinks from "./components/Header/HeaderLinks";
import Hero from "./components/Header/Hero/Hero";
import Login from "./components/auth/Login";
import {
BrowserRouter as Router,
Route,
Switch,
useHistory,
} from "react-router-dom";
import SignUp from "./components/auth/SignUp";
import ViewUserProfile from "./components/Admin/ViewUserProfile";
import AdminHeader from "./components/Admin/AdminHeader";
import ManageBusinessType from "./components/Admin/ManageBusinessType";
import ManageCompany from "./components/Admin/ManageCompany";
import UserDashboard from "./components/Users/UserDashboard";
import ViewProfile from "./components/Users/ViewProfile";
import { ResetPassword } from "./components/Users/ResetPassword";
import ChangePassword from "./components/Users/ChangePassword";
import { useEffect } from "react";
import AuthenticatedRoute from "./components/auth/authRoute/AuthenticatedRoute";
function App() {
return (
<Router>
<Switch>
<AuthenticatedRoute path="/" exact>
<Hero />
</AuthenticatedRoute>
<AuthenticatedRoute path="/admin" exact>
<AdminHeader />
</AuthenticatedRoute>
<AuthenticatedRoute path="/user" exact>
<UserDashboard />
</AuthenticatedRoute>
</Switch>
</Router>
);
}
export default App;
After a successful login it redirects me to the right user role page but it's empty page as the screenshot below.
How can I fix the Issue? I used react-router-dom#5.3.0
Thanks
You are rendering the result of a navigation action instead of JSX. AuthenticatedRoute should render either a Route rendering the routed content or a Redirect to the appropriate path.
Example:
If loggedIn is falsey, then redirect to "/signin", otherwise check the role the route should have access to, and if the role matches render a Route with the props passed through, otherwise redirect to the appropriate user/admin path.
import { useHistory } from "react-router-dom";
import { Route } from "react-router-dom";
const AuthenticatedRoute = ({ roles, ...props }) => {
const tokenn = JSON.parse(localStorage.getItem("desta"));
const user_type = tokenn?.user.roles[0]; //USER or ADMIN
const loggedIn = tokenn?.user_status; //true
return loggedIn
? roles.includes(user_type)
? <Route {...props} />
: <Redirect to={user_type === "USER" ? "/user" : "/admin"} />
: <Redirect to="/signin" />;
};
export default AuthenticatedRoute;
Specify the roles a route should accessible by.
function App() {
return (
<Router>
<Switch>
<AuthenticatedRoute path="/admin" roles={["ADMIN"]}>
<AdminHeader />
</AuthenticatedRoute>
<AuthenticatedRoute path="/user" roles={["USER"]}>
<UserDashboard />
</AuthenticatedRoute>
<Route path="/">
<Hero />
</Route>
</Switch>
</Router>
);
}
I am using Router and customHistory to help me redirect the pages, but the pages not render correctly.
The code works like this: if the user is authorized or log in, then the user should be redirected to "localhost:8080/dashboard" and see the dashboard(with data fetching from firebase) & header; if the use is log out, then the user should be redirect to "locahost:8080/" and see the log in button with the header.
However, after I successfully log in, the url is "localhost:8080/dashboard" without any data fetched from firebase, only things I can see are the header and login button. But if I hit "RETURN" with the current url which is "localhost:8080/dashboard", it will redirect to correct page with all data fetching from firebase, and no login button.
This is the github_link to the code.
I have spent times searching online, but do not find any positive result except this one. After reading the stackoverflow I feel my code has some problems with asynchronization. Any thoughts?
I really appreciate for your help! Thanks!
This is my AppRouter.js:
export const customHistory = createBrowserHistory();
const AppRouter = () => (
<Router history={customHistory}>
<div>
<Header />
<Switch>
<Route path="/" exact component={LoginPage} />
<Route path="/dashboard" component={ExpenseDashboardPage} />
<Route path="/create" component={AddExpensePage} />
<Route path="/edit/:id" component={EditExpensePage} />
<Route path="/help" component={HelpPage} />
<Route component={LoginPage} />
</Switch>
</div>
</Router>
);
This is my app.js
import React, { Children } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import "normalize.css/normalize.css"; //Normalize.css makes browsers render all elements more consistently and in line with modern standards.
import "./styles/styles.scss";
import AppRouter, { customHistory } from "./routers/AppRouter";
import configureStore from "./redux/store/configStore";
import { startSetExpenses } from "./redux/actions/expenses";
import { login, logout } from "./redux/actions/auth";
import "react-dates/lib/css/_datepicker.css";
import { firebase } from "./firebase/firebase";
//for testing: npm test -- --watch
const store = configureStore();
const jsx = (
<Provider store={store}>
<AppRouter />
</Provider>
);
ReactDOM.render(<p>Loading...</p>, document.getElementById("app"));
let hasRendered = false;
const renderApp = () => {
if (!hasRendered) {
ReactDOM.render(jsx, document.getElementById("app"));
hasRendered = true;
}
};
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log("log in");
store.dispatch(login(user.uid));
store.dispatch(startSetExpenses()).then(() => {
renderApp();
if (customHistory.location.pathname === "/") {
customHistory.push("/dashboard");
}
});
} else {
console.log("log out");
store.dispatch(logout());
renderApp();
customHistory.push("/");
}
});
This is the header.js
import React from "react";
import { BrowserRouter, Route, Switch, Link, NavLink } from "react-router-dom";
import { connect } from "react-redux";
import { startLogout } from "../redux/actions/auth";
export const Header = ({ startLogout }) => (
<header>
<h1>Expensify</h1>
<NavLink to="/" activeClassName="is-active">
Dashboard
</NavLink>
<NavLink to="/create" activeClassName="is-active">
CreateExpense
</NavLink>
<button onClick={startLogout}>Logout</button>
</header>
);
const mapDispatchToProps = (dispatch) => ({
startLogout: () => dispatch(startLogout()),
});
export default connect(undefined, mapDispatchToProps)(Header);
I have a create profile page and an auth page (where one enters a code sent by text). I'd like to navigate to the auth page, when a profile is created.
I have a component for the create profile page and one for the auth page.
Based on the example here.
Relevant code:
// CreateProfileComponent.js
import React from 'react';
..
import { withRouter } from "react-router";
class CreateProfile extends React.Component {
constructor(props) {
super(props);
}
handleCreateProfile(e) {
e.preventDefault();
if (condition) {
createProfile(...);
this.props.history.push('/auth');
} else {
// re-render
}
}
render() {
return (
// data-testid="create-profile" - workaround (https://kula.blog/posts/test_on_submit_in_react_testing_library/) for
// https://github.com/jsdom/jsdom/issues/1937
<form onSubmit={this.handleCreateProfile.bind(this)} data-testid="create-profile">
...
</form>
);
}
}
export default withRouter(CreateProfile);
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import CreateProfile from './CreateProfileComponent';
import { TokenEntry } from './TokenEntryComponent';
ReactDOM.render(
<Router>
<ProtectedRoute exact path="/" component={ActivityList} />
<Route path="/login" component={CreateProfile.WrappedComponent} />
<Route path="/auth" component={TokenEntry} />
</Router>,
document.getElementById('root')
);
//createprofilecomponent.test.js
import React from 'react';
import {
LocationProvider,
createMemorySource,
createHistory
} from '#reach/router';
import { render, screen, fireEvent } from '#testing-library/react';
import CreateProfile from '../CreateProfileComponent';
const source = createMemorySource('/login');
const history = createHistory(source);
let displayName = null;
let phoneNumber = null;
let legalAgreement = null;
global.window = { location: { pathname: null } };
function Wrapper({children}) {
return <LocationProvider history={history}>{children}</LocationProvider>;
}
beforeEach(() => {
render(
<CreateProfile.WrappedComponent location={'/'} />,
{ wrapper: Wrapper }
);
});
it("navigates to /auth when good data entered", () => {
// setup
fireEvent.submit(screen.getByTestId('create-profile'));
expect(global.window.location.pathname).toEqual('/auth');
});
I'm getting
TypeError: Cannot read property 'push' of undefined
during tests and in Chrome.
What am I missing?
Use the react-router-dom
import { withRouter } from "react-router-dom";
Changed
export default withRouter(CreateProfile);
To
export const CreateProfileWithRouter = withRouter(CreateProfile);
Changed
<Route path="/login" component={CreateProfileWithRouter.WrappedComponent} />
To
<Route path="/login" component={CreateProfileWithRouter} />
Pull request created to include an example in withRouter's documentation.
Example gist
I am new to react and still learning my way around. I am creating a single page app where the user is redirected to the login page if he is not logged in. React has a Redirect component and I am not sure how to use it.
Following is my app.js :-
import React, { Component } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import Login from './components/login/login.js'
import Protected from './components/protected/protected.js'
import './App.css';
class App extends Component {
state = {
loggedIn: false // user is not logged in
};
render() {
return (
<Switch>
<Route path="/protected" component={Protected}/>
<Route path="/login" component={Login}/>
</Switch>
);
}
}
export default App;
In the above scenario, I want the user to be redirected to the /login page if the state loggedIn is false or else it should go to the /protected page.
I usually create a PrivateRoute component that checks if the user is logged in (via prop, redux, localstorage or something).
Something like:
const PrivateRoute = ({ isLoggedIn, ...props }) =>
isLoggedIn
? <Route { ...props } />
: <Redirect to="/login" />
In the router I then use it for my, well, private routes :)
<Switch>
<PrivateRoute isLoggedIn={ this.state.loggedIn } path="/protected" component={Protected} />
<Route path="/login" component={Login}/>
</Switch>
you can do the following by redux-saga,
import jwt_decode from 'jwt-decode';
//define you saga in store file here
import {store, history} from './store';
// Check for token
if (localStorage.jwtToken) {
// Set auth token header auth
setAuthToken(localStorage.jwtToken);
// Decode token and get user info and exp
const decoded = jwt_decode(localStorage.jwtToken);
// Set user and isAuthenticated
store.dispatch(setCurrentUser(decoded));
// Check for expired token
const currentTime = Date.now() / 1000;
if (decoded.exp < currentTime) {
// Logout user
store.dispatch(logoutUser());
// Clear current Profile
store.dispatch(clearCurrentProfile());
// Redirect to login
window.location.href = '/login';
}
}
const setAuthToken = token => {
if (token) {
// Apply to every request
axios.defaults.headers.common['Authorization'] = token;
} else {
// Delete auth header
delete axios.defaults.headers.common['Authorization'];
}
};
// Set logged in user
export const setCurrentUser = decoded => {
return {
type: SET_CURRENT_USER,
payload: decoded
};
};
// Clear profile
const clearCurrentProfile = () => {
return {
type: CLEAR_CURRENT_PROFILE
};
};
// Log user out
const logoutUser = () => dispatch => {
// Remove token from localStorage
localStorage.removeItem('jwtToken');
// Remove auth header for future requests
setAuthToken(false);
// Set current user to {} which will set isAuthenticated to false
dispatch(setCurrentUser({}));
};
// render part
render() {
return (
<Provider store={store}>
<ConnectedRouter history={history}>
{/* Switch stops searching for the path once the match is found */}
<Switch>
{/* <div> */}
<Route exact path="/" component={Landing} />
<Route exact path="/signup" component={Signup} />
<Route exact path="/login" component={Login} />
<Route exact path="/forgotpassword" component={ForgotPassword} />
<Route exact path="/not-found" component={NotFound} />
<Route exact path="/resetpassword" component={ResetPassword} />
{/* Do not pass 'exact' props. It won't match /dashboard/other_url */}
<Route path="/dashboard" component={DefaultLayout} />
{/* </div> */}
</Switch>
</ConnectedRouter>
</Provider>
);
}
// .store file if you need
import { createBrowserHistory } from 'history'
import {createStore, applyMiddleware } from 'redux';
import { routerMiddleware } from 'connected-react-router'
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
import createSagaMiddleware from 'redux-saga'
import rootReducer from './reducers'
import rootSaga from './sagas/';
export const history = createBrowserHistory();
const sagaMiddleware = createSagaMiddleware()
const myRouterMiddleware = routerMiddleware(history);
const initialState = {};
const middleWare = [myRouterMiddleware,sagaMiddleware];
export const store = createStore(rootReducer(history),
initialState,
composeWithDevTools(applyMiddleware(...middleWare))
);
sagaMiddleware.run(rootSaga);
I have a small react app which has user login, I want to have a private route which will be protected, and only authenticated users can use it.
Here is my code.
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
// Styling
import './App.css';
// Privat route
import PrivateRoute from './routes/PrivateRoute';
// Common
import Navigation from './components/common/Navigation';
// Components
import Layout from './components/Layout'
import Login from './components/Login';
import Home from './components/Home';
import Profile from './components/Profile';
export default () => (
<Router>
<Switch>
<Layout>
{/* Public routes */}
<Route exact path="/" component={ Home } />
<Route exact path="/login" component={ Login } />
{/* Private routes */}
<PrivateRoute exact path="/profile" component={ Profile } />
</Layout>
</Switch>
</Router>
);
PrivateRoute.js
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
import UserService from '../services/User';
export default class PrivateRoute extends React.Component {
state = {
isAuthenticated: null,
user: null
}
async componentDidMount(){
let isAuthenticated, user = null;
try{
user = await UserService.auth();
isAuthenticated = true;
}catch(e){
isAuthenticated = false;
}
this.setState({ isAuthenticated, user });
}
render() {
const { isAuthenticated, user } = this.state;
if(isAuthenticated === null){
return null;
}
return (
isAuthenticated ?
<Route path={this.props.path} component={this.props.component}/> :
<Redirect to={{ pathname: '/login', state: { from: this.props.location }}} />
);
}
}
Home.js
import React, { Component } from 'react';
export default class Home extends Component{
render(){
return (
<div>Home!</div>
);
}
}
I am constantly getting the error
Warning: You tried to redirect to the same route you're currently on: "/login"
even though I am in the home page which is has no business checking if user is authenticated.
I suspect this issue has something to do with my routes and the common layout I am trying to implement, but I cant find the problem.