Navigation - alternative to browserHistory in react-router v4 - javascript

Previously, I was able to reroute user (e.g. on successful login) using browser history:
import { browserHistory } from 'react-router'
browserHistory.replace('/home')
In the new react-router I am no longer able to import browserHistory, what is the alternative way to achieve my goal?

import { Router, Route } from 'react-router'
import { createBrowserHistory } from 'history'
export default () => (
<Router history={createBrowserHistory()} >
<Route exact path="/" component={MainPageComponent} />
</Router>
)
In MainPageComponent you can use this.props.history.push('/home').
Learn about router history here https://github.com/ReactTraining/history

Related

How do you use react-router-dom to redirect user when token has expired?

I have a React application that accesses a Flask API. To access some API routes, the user needs to log in. I am using Axios to do the requests. Then, he receives a token which is stored in the local storage. When this token expires and the user makes another request, I want to redirect him to the login page. However, I don't know how I would do it.
I am treating API request errors with Axios response interceptor. It removes the token from the local storage and then should redirect the user to the login page. Since I am using functional components, I could not find an example that fits well (besides downloading another package called history).
I have tried to use the 'useHistory' hook and Redirect from react-router-dom (with a proper BrowserRouter set up), but it doesn't work.
api.js
import axios from "axios"
import { RemoveAuth } from "./Auth"
export const api = axios.create({
baseURL: "http://localhost:5000/api/",
timeout: 15000,
})
// more code
api.interceptors.response.use(null, (error) => {
if(error.response.status === 401){
RemoveAuth();
}
return error;
});
Auth.js
import { useHistory } from "react-router-dom"
export const RemoveAuth = () => {
let history = useHistory()
localStorage.clear();
history.push('/login')
}
routes.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import PrivateRoutes from "./PrivateRoutes";
import Dashboard from "../pages/dashboard";
import Login from "../pages/login";
import Logout from "../pages/logout";
const Routes = () => (
<BrowserRouter>
<Switch>
<PrivateRoutes exact path="/dashboard" component={Dashboard} />
<PrivateRoutes exact path="/logout" component={Logout} />
<Route exact path="/login" component={Login} />
</Switch>
</BrowserRouter>
);
PrivateRoutes.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthLogin } from "../services/Auth";
const PrivateRoutes = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={() => (AuthLogin() ? <Redirect to="/login" /> : <Component />)}
/>
);
export default PrivateRoutes;
Thanks in advance for the help!
The simplest thing to do is to create your own history object. Something like this:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
Then in your provider pass in your custom history object:
import { Router } from 'react-router-dom'
import history from './utils/history'
ReactDOM.render(
<Router history={history}>
<App />
</Router>
document.getElementById('root')
);
This allows you to utilize your history in non-component code. Just import your history object into your Auth.js file and use it:
import { history } from './utils/history'
export const RemoveAuth = () => {
localStorage.clear();
history.push('/login')
}
As an added bonus, now your history lives in a place that is easily mock-able, so creating testing around it is more straightforward. You can find more information about creating your own custom history object in the docs.

React Router v5: history.push() changes the address bar, but does not change the page

I am trying to redirect a user to a new page if a login is successful in my React app. The redirect is called from the auth service which is not a component. To access the history object outside of my component I followed this example in the React Router FAQ. However, when I call history.push('/pageafterlogin'), the page is not changed and I remain on the login page (based on my Switch I would expect to end up on the 404 page). The URL in the address bar does get changed to /pageafterlogin but the page is not changed from the login page. No errors appear in the console or anything else to indicate my code does not work.
How can I make history.push() also change the page the user is on?
// /src/history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
// /src/App.js
...
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import history from './history';
function App() {
return (
<Router history={history}>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/login" exact render={() => <FormWrapper><LoginForm /></FormWrapper>} />
<Route render={() => <h1>404: not found</h1>} />
</Switch>
</Router>
);
}
export default App;
// src/services/auth.service.js
import axios from 'axios';
import history from '../history';
const API_URL = '...';
class AuthService {
login(username, password) {
return axios.post(API_URL + 'login', {
username,
password
}).then(res => {
if (res.status === 200) {
localStorage.setItem('user', JSON.stringify(res.data));
history.push('/pageafterlogin');
}
});
}
}
Instead of using BrowserRouter, use Router from react-router-dom
You could see the example here
import { Router, Route, Switch, useHistory, create } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import React from 'react';
const history = createBrowserHistory();
export default function App() {
return (
<Router history={history}>
<Switch>
<Route path="/" exact component={() => <h1>HomePage</h1>} />
<Route path="/login" exact component={Login} />
<Route render={() => <h1>404: not found</h1>} />
</Switch>
</Router>
);
}
function Login() {
React.useEffect(() => {
history.push('/pageafterlogin')
}, [])
return <h1>Login page</h1>
}
If you are looking for a solution to this in 2022 and are using React V18+,
the solution is that React v18 does not work well with react-router-dom v5.
I have not tried with react-router-dom v6 yet, but downgrading to React V17 solved the issue for me.
I removed StrictMode and it solved the problem
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import React from 'react';
const history = createBrowserHistory();
export default function MyApp() {
return (
<Router history={history}></Router>
);
}
I had the same problem when I hadn't specified the vesion of 'history'. You need to use a 4.x version with Router 5.x. For example, I use React v18, history v4.7.2 and react-router-dom v5.3.3 and it works fine.
Try
npm i history#4.7.2

ReactJS : Nested navigations not works, not reaching the expected page

Using React ^16.13.1 and react-router-dom ^5.2.0, We have multiple Navigation files to make nested navigation, the first Navigation.js runs and redirects fine, but the second Navigation.js does not work as we expected.
Created a react APP using npx create-react-app nested
Listing the important files for review:
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Navigation from "./Navigation";
import { BrowserRouter } from "react-router-dom";
const App = () => {
return (
<BrowserRouter>
<Navigation />
</BrowserRouter>
);
};
export default App;
Navigation.js
import React from "react";
import { Switch, Route, BrowserRouter } from "react-router-dom";
import nestedNavigation from "./nested/Navigation";
const NotFound = () => <h1>Not Found</h1>;
const Navigation = () => {
return (
<Switch>
<Route exact path="/welcome" component={nestedNavigation} />
<Route path="/" component={NotFound} />
</Switch>
);
};
export default Navigation;
nested/Navigation.js nested navigation - the second one
import React from "react";
import {
Switch,
Route,
BrowserRouter,
useRouteMatch,
} from "react-router-dom";
import Welcome from "../Welcome"
const Navigation = () => {
let { path, url } = useRouteMatch();
debugger;
return (
<Switch>
<Route path={`${path}/nested`} exact component={Welcome} />
</Switch>
);
}
export default Navigation;
Nested routes require the full path in the most recent full release version of React Router, add the rest of the URL from the upper components to the path prop. codesandbox from react-router Docs
Also remove the exact from your welcome. Sub-routes wont likely work with exact because they aren’t exactly that route!

Reactjs Route Doesn't Return Correct Result

I have 3 apps with this structure:
import React from 'react'
export default React.createClass({
render() {
return <div>Hey App1!</div>
}
})
import React from 'react'
export default React.createClass({
render() {
return <div>Hey App2!</div>
}
})
import React from 'react'
export default React.createClass({
render() {
return <div>Hey App3!</div>
}
})
And in my main.js:
import React from 'react'
import ReactDOM from 'react-dom'
import {Router, Route, browserHistory, IndexRoute } from 'react-router'
import App1 from './modules/App1.js'
import App2 from './modules/App2.js'
import App3 from './modules/App3.js'
ReactDOM.render((
<Router history={browserHistory}>
<Route path="/" component={App1}>
<IndexRoute component={App1} />
<Route path="app1" component={App2} />
<Route path="app2" component={App3} />
</Route>
</Router>
), document.getElementById('app'))
But when I try /#/app1 or /#/app2 addresses in my browser it shows the / result (it means 'Hey App1").
My Reactjs version is: 15.3.2
your preferred history is browserHistory. Then the URLs should look like example.com/some/path.
So try /app1 or /app2 without hash in the url.
Hash history uses the hash (#) portion of the URL, creating routes that look like example.com/#/some/path.
If you want to use hash in the url pass hashHistory as props instead of browserHistory

React Router: Failed to Navigate Route

I setup simple route within my index.js file.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import reduxThunk from 'redux-thunk';
import '../less/app.less';
import reducers from './reducers';
import App from './components/App';
import Login from './components/auth/Login';
import Welcome from './components/Welcome';
// const defaultSetting = settings;
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Welcome} />
<Route path="login" component={Login} />
</Route>
</Router>
</Provider>
, document.querySelector('.container')
);
my App.js
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
export default App;
When I navigate to localhost:8080 using webpack-dev-server I can properly show my index route. but when I navigate to localhost:8080/login' it shows errorCannot GET /login`.
Any solution?
By default the server will look for an html file at the /login route. So you should configure it for html5 navigation to return you index.html for any route it receives.
EDIT:
To do so in webpack, as you suggest in the comments, you can add this to your webpack dev server config:
historyApiFallback: true
index.html should be the default, so no need to specify it.
Also please note that urls containing dots are still pointing to files and thus redirected to the server. For example, if you have an url such as /search/firsname.lastname you would need to add a handler for this.
historyApiFallback: {
rewrites: [
{
from: /^\/search\/.*$/,
to: function() {
return 'index.html';
}
}
]
},
See this issue for more info.

Categories