props.history.push() not working in metronic theme - reactjs

After login I am not redirecting to my users page I am using react metronic theme.
I think something is wrong with props.history.push()
Here what I have done so far.
Main component is below:
ReactDOM.render(
<MetronicI18nProvider>
<MetronicLayoutProvider>
<MetronicSubheaderProvider>
<MetronicSplashScreenProvider>
<ToastContainer/>
<App store={store} persistor={persistor} />
</MetronicSplashScreenProvider>
</MetronicSubheaderProvider>
</MetronicLayoutProvider>
</MetronicI18nProvider>,
document.getElementById("root")
);
App component:
export default function App({ store, persistor }) {
return (
/* Provide Redux store */
<Provider store={store}>
{/* Asynchronously persist redux stores and show `SplashScreen` while it's loading. */}
<PersistGate persistor={persistor} loading={<LayoutSplashScreen />}>
{/* Add high level `Suspense` in case if was not handled inside the React tree. */}
<React.Suspense fallback={<LayoutSplashScreen />}>
{/* Override `basename` (e.g: `homepage` in `package.json`) */}
<BrowserRouter>
{/*This library only returns the location that has been active before the recent location change in the current window lifetime.*/}
<MaterialThemeProvider>
{/* Provide `react-intl` context synchronized with Redux state. */}
<I18nProvider>
{/* Render routes with provided `Layout`. */}
<Routes />
</I18nProvider>
</MaterialThemeProvider>
</BrowserRouter>
</React.Suspense>
</PersistGate>
</Provider>
);
}
This is my routers component
<Switch>
{getToken()==null? (
<Route>
<AuthPage/>
</Route>
) : (
<Redirect from="/auth" to="/"/>
)}
<Route path="/error" component={ErrorsPage}/>
{getToken()==null ? (
<Redirect to="/auth/login"/>
) : (
<Layout>
<BasePage/>
</Layout>
)}
</Switch>
this is my base component
export default function BasePage() {
return (
<Suspense fallback={<LayoutSplashScreen/>}>
<Switch>
<Redirect exact from="/" to="/allUsers"/>
<ContentRoute path="/allUsers" component={AllUsers}/>
<Redirect to="error/error-v1"/>
</Switch>
</Suspense>
);
}
this is action which I fire after sign In button
props.onLogin(values).then((res) => {
toast.success('Login Successfull')
props.history.push('/allUsers')
}).catch((err) => {
console.log('error', err)
toast.error('Login failed')
})
and I have same AuthPage component.
api is fired success fully, token is saved successfully and toaster is shown 'Login successful'. But I am not redirecting to my desired page.
But if I refresh then I am redirected to home page.
Can anyone help me with this
** history.push is working in /allUsers route. But not working in Login component
Update: I think I found what I am doing wrong.
I don't need to perform any history.push() action when I will save token(which is I am doing in my onLogin action) it should automatic redirect to AllUsers Page.
I changed my approach to as below and now it is working.
const {isAuthorized} = useSelector(
({mainAuth}) => ({
isAuthorized: mainAuth.user != null,
}),
shallowEqual
);
replaced getToken with isAuthorized
But history.push() not working here is still mystery.

Please use history package version as v4.9.0 for react-router v5.2.0
Working example
https://codesandbox.io/s/quizzical-dan-z3725
Originally answered here: https://stackoverflow.com/a/64623542/1723410

I think in your case you are not getting history reference in props. You can try these two solutions. withRouter from react-router & useHistory from react-router-dom.
import { withRouter } from 'react-router';
componentName(){
const { match, location, history } = this.props
history.push('Your Page')
}
export default withRouter(componentName);
OR
import { useHistory } from "react-router-dom";
component(){
const history = useHistory();
history.push('Your Page')
}

Try this
import { useHistory } from "react-router-dom";
export someclass/ReactComponent{
.
.
.
const history = useHistory();
props.onLogin(values).then((res) => {
toast.success('Login Successfull')
history.push('/allUsers')
}).catch((err) => {
console.log('error', err)
toast.error('Login failed')
})
}
}
Or refer to this thread

I guess I have been able to reproduce your issue here but not sure yet about solution. But my while guess is that your root most Switch doesn't get triggered when you do history.push, so those expressions with getToken are not evaluated and thats why we are not redirected anywhere.
If I have time later to play around more, I might update this answer.

Related

React Routes are not functioning well over LOGOUT?

I am working on a project where i need to integrate Auth functionality, but i counter with a redirection issue. after Logout i am not landing over /auth, it still resides on /dashboard component, but if I refresh the the page it redirects to the /auth component.
Testing Scenario:
once loge In, then same time Logout, it will work fine, will take you to the /auth
once Log In, type in the url auth, it will redirect to the Dashboard same time, which is absolutely fine. but after this, if you try to logout, it will logout, but the url will not redirect to auth.
Logout Functionality
const logout = () => {
dispatch({type: actionTypes.LOGOUT})
history.push('/auth')
setUserProfile(null)
}
Logout Reducer
import { AUTH, LOGOUT, LOGIN_CODE } from '../constants/ActionTypes'
const authReducer = (state={authData: null}, action) => {
switch (action.type) {
case LOGOUT:
localStorage.removeItem("userProfile")
return {...state, authData: null}
default:
return state
}
}
export default authReducer
Routes
<switch>
<Route path="/" component={()=> <Redirect to="/dashboard" />} exact />
<PrivateRoute path="/" component={Dashboard} auth={user} exact />
<PrivateRoute path="/dashboard" component={Dashboard} auth={user} exact />
<PublicRoute path="/auth" component={Login} auth={user} restricted={true} exact />
<swtich>
PrivateRoute Code
import React from 'react';
import {Route,Redirect} from 'react-router-dom';
const PrivateRoute = ({component : Component, auth, ...rest})=>{
return(
<Route {...rest} render={props =>{
if(!auth)
return <Redirect to="/auth"/>
return <Component {...props}/>
}}/>
)
}
export default PrivateRoute;
PublicRoute Code
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const PublicRoute = ({auth, component: Component, restricted, ...rest}) => {
return (
// restricted = false meaning public route
// restricted = true meaning restricted route
<Route {...rest} render={props => (
auth && restricted ?
<Redirect to="/" />
: <Component {...props} />
)} />
);
};
export default PublicRoute;
import Router from 'next/router'
Then after logout send:
Router.reload();
You must Call function localStorage when you logout:
localStorage.clear();
it will redirect you into your auth page.
I searched every possible solution to fix this uncertain behavior of react-router-dom, but did not find any logical solution for this. but now at the end I came up with the hot fix for this type of issue. just need to add the tiny one line snippet in logout functionality. which I mentioned below for best reference.
reload route after logOut
window.location.reload()
this function will not hold the component and url too. once logout functionality run, will redirect to the auth route right away.

React Router gives a blank screen on protected Route

So essentially, I am trying have an authentication workflow. So basically, the Home route is protected, login is not, register is not, and then I have a verifyEmail page that opens if you arent verified.
const PrivateRoute = ({component: RouteComponent,...rest}) =>{
const{currentUser}=useContext(AuthContext)
function route (){
}
return(
<Route
{...rest}
render={routeProps =>
!!currentUser && currentUser != null
?
currentUser.emailVerified ? <RouteComponent {...routeProps}/>:(<Redirect to={"/verifyEmail"}/>)
:
(<Redirect to={"/login"}/>)
}
/>
)
}
export default PrivateRoute
And then in App.js I have
function App() {
return (
<div className={'App'}>
<AuthProvider>
<Router>
<div>
<PrivateRoute path="/" component={HomePage}/>
<Route path="/verifyEmail" component={Verify}/>
<Route path="/login" component={Login}/>
<Route path="/register" component={Register}/>
</div>
</Router>
</AuthProvider>
</div>
);
}
export default App;
Current User is basically the user credentials, I am using Firebase for authentication. The problem I am having is that after logging in it just shows a blank screen, when the email is not verified instead of showing the verifyEmail page.
To elaborate more on the problem, the actual problem is that if a user is not email verified, then it routes to nowhere, and gives me a blank screen so meaning <Redirect to="/verifyEmail" doesnt work. To debug this further I decided to replace that with a Hello</> and I saw a screen with Hello. So I dont think authentication is the problem, just that it doesnt Route to the appropriate page.
Please help.
You can create a HOC (a component) that wrap your Routes and you make your validation there.
Example:
import React, { useEffect } from 'react'
import { useHistory } from 'react-router-dom';
import { useSelector } from 'react-redux';
export default function RequireAuth({ children }) {
const history = useHistory();
const auth = useSelector(state => state.auth)
useEffect(() => {
if(!auth){
history.push("/")
}
}, [auth])
return (
<>
{children}
</>
)
}
This is an example of a personal project where I use react-redux to take auth witch is only a boolean.
I use useHistory of react-router-dom to redirect to "/" in case the user isn't logged in.
And finally in my App.js:
<div>
<RequireAuth>
<Route path='/post' component={PostPage} />
</RequireAuth>
<Route path='/' exact component={LoginPage} />
</div>

How to protect react routing when session (stored cookie) is not accessible?

I'm looking for a efficient way to prevent unauthorized people from accessing specific routing paths.
The cookie that i get from the backend is not readable so i can't do much with it.
I have two endpoints:
/token (POST) to get the cookie with the token
/account (GET) to get the username and role
Short explaination of what i did so far:
Protected all routing by wrapping them with a PrivateRoute component
A redux action is fired after user is attempting to log in. This action calls an api which returns a cookie (jwt) and the data of the user. Username and role will be saved into the 'authStore'. If login succeed the attribute 'isAuthenticated' in the same store is set to True. Only when this boolean is set to true, user can access the wrapped routes.
My problem:
If I close my tab and open a new one, the store resets (which is fine and normal). The cookie is still available tho. So for a good UX I would like to authenticate the user directly. At the moment the user is redirected to the login page. This makes sense because the PrivateRoute component is only accessible if the store attribute isAuthenticated is true. So the user has to login again to update the store.
I tried to dispatch an action in App.js from the method componentDidMount to get the user credentials directly but that didn't help. Since render() is being fired first it won't help.
This is my code:
App.js:
export class App extends PureComponent {
componentDidMount() {
// Action to retrieve user credentials (if available then saves it to the authStore and sets isAuthenticated to true)
this.props.initloginRequest();
}
render() {
return (
<div className="d-flex flex-column h-100 w-100 bg-grey main-container">
<Sprites className="d-none" />
<Router>
<Header />
<Switch>
<Route path="/login" exact component={X1} />
<PrivateRoute path="/" exact component={X2} />
<PrivateRoute path="/details" exact component={X3} />
<PrivateRoute path="/finish" exact component={X4} />
</Switch>
</Router>
</div>
);
}
}
export class PrivateRoute extends Component {
render() {
const { isAuthenticated, component, ...rest } = this.props;
const renderComponent = (props) => {
if (isAuthenticated) {
const ComponentToShow = component;
return <ComponentToShow {...props} />
} else {
return <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
}
};
return (
<Route
{...rest}
render={renderComponent}
/>
)
}
}
export default connect(
(state) => ({
isAuthenticated: state.authStore.isAuthenticated
})
)(PrivateRoute);
There is a possible solution for this by making an api call to /account based on the status code it returns. Whenever the route is changed the call will be made so that is not really what i want. I just want a solution that requires one call at the beginning of the application.
Thanks in advance
I've solved this issue by dispatching an action in index.js (src path):
import './index.css';
import App from './App';
import { initloginRequest } from './actions/auth';
const store = configureStore();
store.dispatch(initloginRequest())
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root'),
);
This action calls an api and checks if the user is authenticated. So when it reaches my PrivateRoute component, it knows in which direction it should redirect the user.
You should set isFetching in your store/state on true till the action finishes to prevent the App.js from rendering your routes.
export class App extends PureComponent {
render() {
const { isFetching } = this.props
if (isFetching) {
return <div>Loading application...</div>
}
return (
<div className="d-flex flex-column h-100 w-100 bg-grey main-container">
<Sprites className="d-none" />
<Header />
<Switch>
...private route etc...
</Switch>
</div>
);
}
}
export default connect(
(state) => ({
isFetching: state.authStore.isFetching,
}),
(dispatch) => bindActionCreators({
}, dispatch),
)(App);
This should solve the problem.

React router dom redirect problem. Changes url, does not render component

Problem: When I use history.push(), I can see that browser changes url, but it does not render my component listening on the path. It only renders if I refresh a page.
App.js file:
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import { Provider } from "react-redux";
import PropTypes from "prop-types";
//Components
import LoginForm from "../LoginForm/LoginForm";
import PrivateRoute from "../PrivateRoute/PrivateRoute";
import ServerList from "../ServerList/ServerList";
const App = ({ store }) => {
const isLoggedIn = localStorage.getItem("userToken");
return (
<Router>
<Provider store={store}>
<div className="App">
{isLoggedIn !== true && (
<Route exact path="/login" component={LoginForm} />
)}
<PrivateRoute
isLoggedIn={!!isLoggedIn}
path="/"
component={ServerList}
/>
</div>
</Provider>
</Router>
);
};
App.propTypes = {
store: PropTypes.object.isRequired
};
export default App;
Inside my LoginForm, I am making a request to an API, and after doing my procedures, I use .then() to redirect my user:
.then(() => {
props.history.push("/");
})
What happens: Browser changes url from /login to /, but component listening on / route is not rendered, unless I reload page.
Inside my / component, I use useEffect() hook to make another request to API, which fetches data and prints it inside return(). If I console.log inside useEffect() it happens twice, I assume initial one, and when I store data from an API inside component's state using useState() hook.
EDIT: adding PrivateRoute component as requested:
import React from "react";
import { Route, Redirect } from "react-router-dom";
const PrivateRoute = ({ component: Component, isLoggedIn, ...rest }) => {
return (
<Route
{...rest}
render={props =>
isLoggedIn === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/login" }} />
)
}
/>
);
};
export default PrivateRoute;
What I tried already:
1) Wrapping my default export with withRouter():
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoginForm));
2) Creating custom history and passing it as prop to Router.
react-router-dom version is ^5.0.1. react-router is the same, 5.0.1
You have at two mistakes in your code.
You are not using <switch> component to wrap routes. So all routes are processed at every render and all components from each <route> are rendered.
You are using local store to exchange information between components. But change in local store is invisible to react, so it does not fire component re-rendering. To correct this you should use local state in App component (by converting it to class or using hooks).
So corrected code will look like
const App = ({ store }) => {
const [userToken, setUserToken] = useState(localStorage.getItem("userToken")); // You can read user token from local store. So on after token is received, user is not asked for login
return (
<Router>
<Provider store={store}>
<div className="App">
<Switch>
{!!userToken !== true && (
<Route exact path="/login"
render={props => <LoginForm {...props} setUserToken={setUserToken} />}
/>
)}
<PrivateRoute
isLoggedIn={!!userToken}
path="/"
component={ServerList}
/>
</Switch>
</div>
</Provider>
</Router>
);
};
And LoginForm should use setUserToken to change user token in App component. It also may store user token in local store so on page refresh user is not asked for login, but stored token is used.
Also be sure not to put anything between <Switch> and </Switch> except <Route>. Otherwise routing will not work.
Here is working sample

How to properly Redirect with React Router 4?

A little background. I am using Firebase Authentication (GitHub, Twitter and Facebook), when the user gets authenticated, the app should redirect to Updater component from the SocialButtonList component.
Now I am trying to use Redirect to: string when the user gets authenticated, but for some reason is not working, maybe I am misunderstanding the docs.
My top level container
const App = () => {
return (
<Router>
<div>
<Switch>
<Route path='/updater' component={Updater} />
<Route exact path='/' component={Login} />
<Route component={NoMatch}/>
</Switch>
</div>
</Router>
);
}
Login.js
const Home = () => {
return (
<Content align='center'>
...
<SocialButtonList size={SOCIAL_BUTTON_SIZE_BIG} />
...
</Content>
);
}
SocialButtonList.js
This component handles the authentication logic and upon authenticating I call authHandler and from here I am attempting to redirect to the 'Updater' component.
authHandler = authData => {
if (authData) {
<Redirect to='/updater' />
console.log('AUTHENTICATED');
} else {
console.info('User is signed out!');
}
}
In console I can see AUTHENTICATED message, but the app does not redirect. How can I properly redirect to Updater component?
Update I found a way to redirect using withRouter. See my answer below in the answer section.
Well, I found my answer using withRouter instead of Redirect
import { withRouter } from 'react-router-dom';
...
authHandler = authData => {
if (authData) {
//<Redirect to={Updater} />
this.props.history.push('/updater');
console.log('AUTHENTICATED');
} else {
console.info('User is signed out!');
}
}
...
export default withRouter(SocialButtonList);
This successfully redirects to Updater component.
Now I am wondering, is there a way to get Redirect to work?

Resources