Could you please help me in understanding the redirection mechanism I could use with latest version of react router ( v1.1.0 ) . I would like to redirect to a url depending on the success or failure of user login .
I have tried to do the following
First created a history using.
let history = createBrowserHistory();
then tried to push the state using
history.pushState(null, 'abc')
Nothing is happening. Could you please let me know the correct way to do transitions .From the docs I understood that transitionTo() API is not present in the latest versions.
It will be great If you could point to a simple working example.
Wanted to update this thread because I spent a good amount of time digging around on this. In React Router 2.0.x, replaceState is deprecated in favor of replace. See here for details: https://github.com/ReactTraining/react-router/blob/v2.0.0/upgrade-guides/v2.0.0.md#link-to-onenter-and-isactive-use-location-descriptors
The correct way to do this would be something like this:
function requireAuth(nextState, replace) {
if (!userExists()) {
replace({
pathname: '/signin',
state: { nextPathname: nextState.location.pathname }
})
}
}
export const renderRoutes = () => (
<Router history={browserHistory}>
<Route path="protectedRoute" component={Protected} onEnter={requireAuth} />
<Route path="signin" component={SignIn} />
</Route>
</Router>
);
Then, in the SignIn component, you can redirect after a successful sign in like this:
signInFunction({params}, (err, res) => {
// Now in the sign in callback
if (err)
alert("Please try again")
else {
const location = this.props.location
if (location.state && location.state.nextPathname) {
browserHistory.push(location.state.nextPathname)
} else {
browserHistory.push('/')
}
}
})
You can register "hooks" on your routes that get triggered when you enter and leave the routes. Check out the documentation for onEnter and onLeave hooks.
There is also an example of requiring auth on a route and redirecting to a different path if the user is not logged in.
Here's a snippet taken from the require auth example within app.js:
function requireAuth(nextState, replaceState) {
if (!auth.loggedIn())
replaceState({ nextPathname: nextState.location.pathname }, '/login')
}
// And from the route configuration, use the requireAuth function in onEnter...
<Router history={history}>
<Route path="/" component={App}>
<Route path="login" component={Login} />
<Route path="logout" component={Logout} />
<Route path="about" component={About} />
<Route path="dashboard" component={Dashboard} onEnter={requireAuth} />
</Route>
</Router>
The nextState and replaceState arguments are objects from rackt/history and get injected into the method you pass into onEnter.
React Router v4.2
I am using React-16.2 & React-router-4.2
and I get solution by this
this.props.history.push("/");
My working code:
.then(response => response.json())
.then(data => {
if(data.status == 200){
this.props.history.push("/");
console.log('Successfully Login');
}
})
I was following this document redirect-on-login-and-logout
#terranmoccasin 's answer is correct. However there is a common need very few examples address.
Let's say you need to secure several routes (dashboard1, dashboard2, ...). How do you redirect back to the original page once you log in successfully? In other words, what do you do with {nextPathname: nextState.location.pathname}?
Here's what I do in ./containers/LoginContainer.js:
import { push } from 'react-router-redux';
const mapStateToProps = (state) => ({
nextPathname: state.routing.locationBeforeTransitions.state.nextPathname,
});
const mapDispatchToProps = (dispatch) => ({
changeLocationOnSignIn: (nextPathname) => {
dispatch(push(nextPathname));
},
});
and in ./components/Login.js
componentWillReceiveProps(nextProps) {
// user signed in or signed up, assuming redux. you may use this elsewhere.
if (nextProps.user.status === 'authenticated' && nextProps.user.user &&
!nextProps.user.error) {
this.props.changeLocationOnSignIn(this.props.nextPathname);
}
React-router 2.4.0 (April 2016) introduced withRouter which creates a HOC. However it wraps React.createClass, not a JS class. I haven't been able to get it working with redux-form, etc. Besides I think the above code is easier to comprehend.
i want just share the actual answer at 2020 year.
The main way for storing previous location in state is the same. But onEnter was removed from library. Now we can use AuthRoute as in the documentation:
<AuthRoute exact path="/food">
<Food />
</AuthRoute>
<Route exact path="/login">
<Login />
</Route>
const AuthRoute = ({ children, isAuthorized, ...rest }) => {
const loginLink = usePrepareLink({
to: "/login",
isRelativePath: true
});
return (
<Route {...rest} render={({ location }) =>
isAuthorized ? (
children
) : (
<Redirect to={{
...loginLink,
state: { from: location }
}} />
)
} />
);
};
and we can use the state to restore previouse URL after login
const onSignIn = useCallback(() => {
setIsAuthorized(value);
const link = (state && state.from) || "/restore-prevented-route";
history.replace(link);
}, [setIsAuthorized, value, history, state]);
The details you can find here (or RU)
This helps me.
Redirect to Login After Logout
import { useHistory } from "react-router-dom";
const history = useHistory();
history.push("/login");
onEnter no longer exists on react-router-4, You can make use of <Route render={ ... } /> to achieve the same functionality.
Here is an example of the same.
<React.Fragment>
<Switch>
<Route path="/dashboard" render={() => (isAuth() ? <Redirect to="/login" /> : <DashboardRoutes />)} />
<Route path="/login" component={Login} />
</Switch>
</React.Fragment>
isAuth() in my case is a function that basically check whether we have the auth token or not and returns true/false based on that.
function isLoggedIn() {
if (!localStorage.getItem('token')) {
return true;
}
return false;
}
As #JohnSz mentions I too had issues with using withRouter. Instead I did it as instructed here:
https://github.com/reactjs/react-router/blob/master/upgrade-guides/v2.0.0.md#programmatic-navigation
const RouteComponent = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired
},
someHandler() {
this.context.router.push(...)
}
})
Basically:
Define contextType
Use this.context.router.push(...)
Cheers.
You can set a condition for the success and failure. Then use the useNavigate hook.
import { useNavigate } from "react-router-dom";
function useLogoutTimer() {
const userIsInactive = useFakeInactiveUser();
const navigate = useNavigate();
useEffect(() => {
if (userIsInactive) {
fake.logout();
navigate("/url");
}
}, [userIsInactive]);
}
Read more: https://reactrouter.com/en/main/hooks/use-navigate
Related
First of all, I am pretty familiar with the withRouter HoC, however, in this case, it doesn't help because I do not want to access the history object in a component.
I am trying to achieve a mechanism that will redirect the user to the login page if I receive back a 401 from a API endpoint. For making http requests I am using axios. I have around 60 endpoints that I need to cover, that are used in a dozen of components throughout my app.
I want to create a decorator function to the axios instance object, that:
1. makes the request
2. if fail && error_code = 401, update user route to `/login`
3. if success, return promise
The problem I have with the above is to update the route of the user. Previously, in react-router-v3, I could have imported the browserHistory object directly from the react-router package, which is no longer possible.
So, my question is, how can I access the history object outside of the React Component without passing it trough the call stack?
react-router v4 also provides a way to share history via the history package, namely createBrowserHistory() function.
The important part is to make sure that the same history object is shared across your app. To do that you can take advantage of the fact that node modules are singletons.
Create a file called history.js in your project, with the following content:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
You can then just import it in your application via:
import history from "./history.js";
Please note that only Router accepts the history prop (BrowserRouter does not), so be sure to update your router JSX accordingly:
import { Router } from "react-router-dom";
import history from "./history.js";
// and then in your JSX:
return (
<Router history={history}>
{/* routes as usuall */}
</Router>
)
A working example can be found at https://codesandbox.io/s/owQ8Wrk3
Today, I faced the same issue. Maybe my solution helps somebody else.
src/axiosAuthenticated.js
import axios from 'axios';
import { createBrowserHistory } from 'history';
const UNAUTHORIZED = 401;
axios.interceptors.response.use(
response => response,
error => {
const {status} = error.response;
if (status === UNAUTHORIZED) {
createBrowserHistory().push('/');
window.location.reload();
}
return Promise.reject(error);
}
);
export default axios;
Also, if you want to intercept any request to add token stored in LocalStorage:
let user = JSON.parse(localStorage.getItem('user'));
var authToken = "";
if (user && user.token)
authToken = 'Bearer ' + user.token;
axios.defaults.headers.common = {'Authorization': `${authToken}`}
To use it, instead of importing from 'axios', import from 'axiosAuthenticated' like this:
import axios from 'utils/axiosAuthenticated'
Here is a solution that worked for me in latest version(5.2.0)
router/index.js
import { BrowserRouter, Switch } from "react-router-dom";
import { Routes } from "./routes";
export const Router = () => {
return (
<BrowserRouter>
<Switch>
<Routes />
</Switch>
</BrowserRouter>
);
};
router/routes.js
import React, { createRef } from "react";
import { Route, useHistory } from "react-router-dom";
import { PageOne, PageTwo, PageThree } from "../pages";
export const historyRef = createRef();
export const Routes = () => {
const history = useHistory();
historyRef.current = history;
return (
<>
<Route exact path="/" component={PageOne} />
<Route exact path="/route-one" component={PageTwo} />
<Route exact path="/route-two" component={PageThree} />
</>
);
};
And use it as below
historyRef.current.replace("/route-two");
I just encountered this same issue, and following is the solution I used to solve this problem.
I ended up creating a factory function which returns an object that has all my services functions. In order to call this factory function, an object with the following shape must be provided.
interface History {
push: (location: string) => void;
}
Here is a distilled version of my factory function.
const services = {};
function servicesFactory(history: History) {
const countries = countriesFactory(history);
const local = {
...countries,
};
Object.keys(local).forEach(key => {
services[key] = local[key];
});
}
Now the file where this function is defined exports 2 things.
1)This factory function
2)the services object.
This is what the countries service looks like.
function countriesFactory(h: History): CountriesService {
const countries: CountriesService = {
getCountries() {
return request<Countries>({
method: "get",
endpoint: "/api/countries",
}, h)
}
}
return countries;
}
And finally here is what my request function looks like.
function request<T>({ method, endpoint, body }: Request, history: History): Promise<Response<T>> {
const headers = {
"token": localStorage.getItem("someToken"),
};
const result: Response<T> = {
data: null,
error: null,
};
return axios({
url: endpoint,
method,
data: body,
headers,
}).then(res => {
result.data = res.data;
return result;
}).catch(e => {
if (e.response.status === 401) {
localStorage.clear();
history.push("/login");
return result;
} else {
result.error = e.response.data;
return result;
}
});
}
As you can see the request function exepcts to have the history object passed to it which it will get from the service, and the service will get it from the services factory.
Now the cool part is that I only ever have to call this factory function and pass the history object to it once in the entire app. After that I can simply import the services object and use any method on it without having to worry about passing the history object to it.
Here is the code of where I call the services factory function.
const App = (props: RouteComponentProps) => {
servicesFactory(props.history);
return (
// my app and routes
);
}
Hope someone else who finds this question will find this useful.
I am providing my solution here as accepted answer does not address the new versions of React Router and they require reload of the page to make that solution work.
I have used the same BrowserRouter. I have created a class with static functions and a member history instance.
/*history.js/
class History{
static historyInstance = null;
static push(page) {
History.historyInstance.push(page);
}
}
/*app-router.js/
const SetHistoryInstance = () => {
History.historyInstance = useHistory();
return (null);
};
const AppRouter = () => {
return (
<BrowserRouter>
<SetHistoryInstance></SetHistoryInstance>
<div>
<Switch>
<Route path={'/'} component={Home} />
<Route path={'/data'} component={Data} exact />
</Switch>
</div>
</BrowserRouter>
)};
Now you can import history.js anywhere in your app and use it.
One simple way is to useHistory() in App.js and then use render and pass history as an attribute of the component:
function App() {
const history = useHistory();
<Router>
<Route
path={nav.multiCategoriesNoTimer}
render={() => <MultiCategoriesNoTimer history={history} />}
/>
</Router>
}
const MixMultiGameNoTimer = (props: any) => {
if (true) {
return (
<NoQuestionsHereScreen history={props.history} />
);
}
}
const NoQuestionsHereScreen = (props: any) => {
return (
<div className='no-questions-here' >
<Button
title="Go back"
onClick={() => props.history.push(nav.home)}
/>
</div>
);
};
There is a bit of drilling, but it works and that for many future versions too>
I created a solution that could solve this issue.
Access react router dom history object outside React component
I think this approach will work with both React-router v4 and v5.
In my app, I'd like to match both the path and the hash to different components. For example:
/pageA#modalB
Would show PageA as the main page, with modalB over the top.
I've tried the following, with many variations of the path property:
<Route path="#modalB" component={modalB}/>
But nothing works.
In React Router 2 inside a modal 'controller' component, I would use:
browserHistory.listen( (location) => { //do something with loction.hash })
I was hoping for something a little more elegant in V4
Not out of the box, but the beauty of React Router 4 is that it's incredibly easy to implement this yourself.
let HashRoute = ({ component: Component, path, ...routeProps }) => (
<Route
{...routeProps}
component={({ location, ...props }) =>
location.hash === path && <Component {...props} />
}
/>
)
<HashRoute path="#modalB" component={ModalB} />
#azium answer works fine as long as you don't need to use render or children prop in HashRoute.
In which case this solution will work better :
import React from 'react';
import { Route } from 'react-router-dom';
const HashRoute = ({ hash, ...routeProps }) => (
<Route
render={({ location }) => (
(location.hash === hash) && <Route {...routeProps} />
)}
/>
);
export default HashRoute;
Use it like that :
<HashRoute hash="#modalB" component={ModalB} />
Or combine it with route matching :
<HashRoute hash="#modalB" path="/subPageOnly" component={ModalB} />
if you actually want to match and get the parameters, use matchPath.
import { useLocation, matchPath } from 'react-router-dom';
// your route you want to see if it matches
const routePath = '/overtherainbow/:country/#/city/:city/detail'
// somewhere while rendering
const location = useLocation();
useEffect(() => {
const matched = matchPath(location.pathname + location.hash, routePath);
if (matched){
// matched, do something with it, like setting state, fetching data or what not
console.log(matched.params); // will be {country:..., city:...}
}
}, [location])
When visitors come to my website, if they're not logged in, they'll be redirected to a login page. This is done by the onEnter() (see code below). When redirect happens, I'd like to access and store the original path. But somehow I keep getting the post-redirect path, i.e. /login, from the redux store. How can I get the original one?
Here is the simplified code in my routes.js (I also use redux, hence the store).
...
export default (store) => {
const requireLogin = (nextState, replace, cb) => {
function checkAuth() {
const { auth: {loggedIn} } = store.getState();
if (!loggedIn) {
replace('/login');
}
cb();
}
console.log(store.getState().routing.location.path); // here I always get the post-redirect path as opposed to the pre-redirect path
if (!isAuthLoaded(store.getState())) {
store.dispatch(loadAuth()).then(checkAuth);
} else {
checkAuth();
}
};
return (
<Route path="/" component={App}>
<Route onEnter={requireLogin}>
<Route path="postLoginPage" component={postLoginPage} />
</Route>
<Route path="login" component={Login} />
</Route>
);
};
I'm using react, redux and react router amoung others to build and example app.
I'm trying to load asynchronously different parts of my application. I've divided my app in ducks and I'm following this example https://github.com/insin/react-examples/tree/master/code-splitting-redux-reducers
But I'm getting this error:
Uncaught Invariant Violation: The root route must render a single element
When trying to get async component with getComponent method of react router.
I'm using:
react-router 2.0.1
My routes:
export default function configureRoutes(reducerRegistry) {
return(
<Route>
<Route component={Landing}>
<Route path='/login' component={Login}/>
<Route path='/register' component={Register}/>
</Route>
<Route path="admin" getComponent={(location, cb) => {
require.ensure([], require => {
cb(null, require('./containers/admin'))
})
}}/>
<Route component={App}>
<Route path='/' component={Home} />
</Route>
</Route>
)}
My component
class Admin extends Component {
componentDidMount() {
this.props.load()
}
render() {
const { message, isFetching } = this.props
return (
<div>
<p>{message}</p>
<p>This module was loaded via chunk </p>
{loading && <p>Doing some fake loading ...</p>}
</div>
)
}
}
Admin.propTypes = {
message: PropTypes.string.isRequired,
isFetching: PropTypes.bool.isRequired,
load: PropTypes.string.isRequired
}
const mapStateToProps = state => state.admin
function mapDispatchToProps(dispatch) {
return bindActionCreators({ load }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Admin)
Does anyone have the same error? any ideas? Anyone have something similar working?
Thanks community!
Update: Added index.js for clarity
import configureRoutes from './routes'
import configureStore from './store/configureStore'
import coreReducers from './modules/core'
import ReducerRegistry from './reducer-registry'
var reducerRegistry = new ReducerRegistry(coreReducers)
// Configure hot module replacement for core reducers
if (process.env.NODE_ENV !== 'production') {
if (module.hot) {
module.hot.accept('./modules/core', () => {
var nextCoreReducers = require('./modules/core')
reducerRegistry.register(nextCoreReducers)
})
}
}
const routes = configureRoutes(reducerRegistry)
const store = configureStore(reducerRegistry)
render(
<I18nextProvider i18n={i18n}>
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>
</I18nextProvider>,
document.getElementById('root')
)
I think your root <Route> is missing the component field.
You need to specify either component or getComponent for every parent route, as this will be the component that the current child route’s component gets passed to as this.props.children.
Rather than
export default function configureRoutes(reducerRegistry) {
return (
<Route>
you want something like
export default function configureRoutes(reducerRegistry) {
return (
<Route component={App}>
In this case, you probably won’t need another App route below.
When use the Link, router works as expected, though i get a warning [history] pushState is deprecated; use push instead.
Using routeActions from react-router-redux does't work, url was change (http://localhost:3002/addCity), but view still the same (Home) or show error if i go to page by url for example: localhost:3002/addCity.
git: https://github.com/kirsanv43/Weather.git
reducers:
export default combineReducers({
cities,
routing: routeReducer
});
store config: https://github.com/kirsanv43/Weather/blob/master/app/redux/config/store.js
import rootReducer from '../reducers'
export default function configureStore(initialState) {
const history = createHistory();
const middleware = syncHistory(history)
const finalCreateStore = compose(
applyMiddleware(middleware)
)(createStore)
const store = finalCreateStore(rootReducer)
middleware.listenForReplays(store);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default
store.replaceReducer(nextRootReducer)
})
}
return store
}
Router:
const store = configureStore()
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="addCity" component={AddCity}/>
</Route>
</Router>
</Provider>
,
document.getElementById('root')
);
Component:
class CitiesList extends React.Component {
onAddCity = () => {
this.props.route.push('/addCity')
};
render() {
return <div className="weatherContainer">
<ul>
<li className="listItem addBtn"><a onClick={this.onAddCity}><span>ADD CITY</span></a></li>
</ul>
</div>
}
}
function mapDispatchToProps(dispatch) {
return {
route:bindActionCreators(routeActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CitiesList)
insted createHistory need use createHashHistory from 'history'
that working for me
Please try to change "react-router-redux" version to "^5.0.0-alpha.8". It solved my problem