I am using react-router 2. My routes are defined as
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="/about" component={About}/>
<Route path="/login" component={Login} onEnter={redirectToDashboard}/>
<Route path="/logout" component={Logout} onEnter={logoutSession}/>
<Route path="/dashboard" component={Dashboard} onEnter={redirectToLogin}/>
</Route>
Everything working fine but I am having problem disabling back button from my dashboard page.
After successful login I am redirecting user to dashboard page but when user clicks back button it goes to login page again. I want to disable back button of browser when user is on dashboard page.
Your best bet, is when the user is login he/ she is redirected to dashbaord. if for some reason the user click on back button you should:
if the user is logged in
stay on the page dashboard
if(logged) {
history.pushState(null, null, location.href);
window.onpopstate = function(event) {
history.go(1);
};
}
it will be not possible to go back.
Applying all these hacks the URL changes to login for a moment and then to wherever-we-push.
Instead, what we can do is: In login, where api endpoint returns success, do:
history.replace('/Whatever_screen')
This will remove login screen from window.history stack, and the screen will not flicker.
On your page which you want to disable back (example, on LoginApp ) add this block, to disable web history back
componentDidMount() {
window.history.pushState(null, document.title, window.location.href);
window.addEventListener('popstate', function (event){
window.history.pushState(null, document.title, window.location.href);
});
}
it's not possible to disable browser buttons.
my advice is to redirect user back to dashboard page if he/she is logged
Actually you can't disable back button. You can use a hack by preventing browser's "back" action. Just add to your Dashboard component compnentWillMount() lifecycle method some code that will trigger browser's "forward" action:
componentWillMount() {
setTimeout(() => {
window.history.forward()
}, 0)
window.onunload=function(){null};
}
But most probably a better solution would be some redirection based on users logged state.
in your login screen add replace to /dashboard
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom'
import createBrowserHistory from 'history/createBrowserHistory'
const history = createBrowserHistory()
class LoginPage extends Component {
componentDidMount(){
history.replace({ pathname: '/dashboard' })
}
render() {
const { history } = this.props
return (
<div>
<h1>Login Page</h1>
<button onClick={() => {
login().then(() => {
history.push('/dashboard')
})
}}>Login</button>
</div>
);
}
}
export default withRouter(LoginPage);
The reason is replace your current path (/login) to /dashboard. Before adding this, please make sure you setup your authentication correctly.
In order to improve the code reusability, we can add an event listener in our index.html and dispatch the browser back disable event from all of our componentDidMount() methods.
In the index.html,
window.addEventListener('navigationhandler', function (e) {
window.history.pushState(null, document.title, window.location.href);
window.addEventListener('popstate', function (event) {
window.history.pushState(null, document.title, window.location.href);
});
});
In React componentDidMount() method,
componentDidMount() {
window.dispatchEvent(new CustomEvent("navigationhandler"));
}
It's not possible to disable browser buttons. But we can use history methods like listen(), go() and push() to override the default behaviour of back button in react.js. Also try to use withRouter().
The following is the sample code for doing this. Please look into componentDidMount() and componenetDidUnmount() methods.
import React from "react";
import { Redirect, Switch, Route, withRouter } from "react-router";
import Page1 from "./Page1";
import Page2 from "./Page2";
import Page3 from "./Page3";
class App extends React.Component {
constructor(props) {
super(props);
// Store the previous pathname and search strings
this.currentPathname = null;
this.currentSearch = null;
}
componentDidMount() {
const { history } = this.props;
history.listen((newLocation, action) => {
if (action === "PUSH") {
if (
newLocation.pathname !== this.currentPathname ||
newLocation.search !== this.currentSearch
) {
this.currentPathname = newLocation.pathname;
this.currentSearch = newLocation.search;
history.push({
pathname: newLocation.pathname,
search: newLocation.search
});
}
} else {
history.go(1);
}
});
}
componentWillUnmount() {
window.onpopstate = null;
}
render() {
return (
<Switch>
<Route exact path="/" render={() => <Redirect to="/page1" />} />
<Route path="/page1" component={Page1} />
<Route path="/page2" component={Page2} />
<Route path="/page3" component={Page3} />
</Switch>
);
}
}
export default withRouter(App);
For more: Disable react back button
Simple and Sweet.
No need to mess with history stack.
solution even does not depend on react router.
It will even prevent the current component from unmounting when back button is clicked, hence it also preserves state of the app as well.
// prevent back
useEffect(() => {
window.addEventListener('popstate', (e) => {
window.history.go(1);
});
}, []);
Related
I came back to react world after a few years. And things certainly have changed for good. I'm using MemoryRouter for my app. And I can navigate fine by using Link. But useNaviate hook is not working as expected. It does nothing on the page. Could you please help me here? Here is my code:
Router:
<MemoryRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</MemoryRouter>
Here is how I'm trying the navigation:
function Home() {
// demo purpose
const navigate = useNavigate()
navigate('/dashboard')
}
I'm not sure if I'm using it right, or if I need to do something else here.
The code is calling navigate as an unintentional side-effect directly in the function component body.
Either call navigate from a component lifecycle or callback to issue an imperative navigation action:
function Home() {
const navigate = useNavigate()
useEffect(() => {
if (/* some condition */) {
navigate('/dashboard');
}
}, [/* dependencies? /*]);
...
}
Or conditionally render the Navigate component to a declarative navigation action:
function Home() {
...
if (/* some condition */) {
return <Navigate to="/dashboard" />;
};
...
}
The problem was that I was calling navigate directly when the component was rendering. It should either be called in an event, or it should be called in useEffect hook.
Make your navigate in function call or in useEffect like this:
function Home() {
// demo purpose
const navigate = useNavigate()
useEffect(() => {
navigate('/dashboard')
}, []);
}
I am using React, and I am trying to detect the previous path / url that the user is coming from within my app. so if they are coming from /register and land on /index, then how do I log, on /index, that the user came from /register. Is this possible?
you can save previous path in a componentWillReceiveProps lifecycle method.
<Route component={App}>
{/* ... other routes */}
</Route>
const App = React.createClass({
getInitialState() {
return { prevPath: '' }
},
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
this.setState({ prevPath: this.props.location })
}
}
})
I want to make a component able to redirect when not loggedIn.
Components were made with react, checking auth function works well with redux.
//App.js
class App extends Component {
checkUserInfo () => {
const loggedInfo = storage.get('loggedInfo');
if(!loggedInfo) return;
const { UserActions } = this.props;
UserActions.setLoggedInfo(loggedInfo)
}
constructor(props) {
super(props);
this.checkUserInfo();
}
render() {
console.log(this.props.logged)
return(...)
}
}
export default connect((state) => ({logged: state.user.get('logged')}, (dispatch)=> ...)
and UserActions.setLoggedInfo action is look like this.
...
export default handleActions({
[SET_LOGGED_INFO]: (state, action) => {
return state.set('logged', true)
}
})
...
So, I want situation that component is redirected when auth is not logged in. I made a rendering component <Route/> with condition which is that if state.logged==false, <Redirect to='login/>.
But in very front point, logged is false before executing checkUserInfo function. so when I'm loggedIn, Redirect to /login, and when I'm not loggedIn, Redirect to /login too.
//PrivateRoute.js
...
render() {
const { logged } = this.props;
console.log(logged);
return(
<Route path="/myPage" render={props =>
logged ? <Component/> : <Redirect to={{pathname: '/login'}}/>
}/>
)
}
...
this is screenshot what is logged value in console.
I want to skip very front state before set state by myFunction(checkUserInfo), how can I do.
plz help me.
and sorry to not good english syntax.
You need to check your global state before rendering the private component.
render prop provided by Route is a good place for that
<Route path='/secretarea' render={() =>{
return props.isLoggedIn ? <SecretComp /> : <Login />
}}/>
Set PrivateRoute like this
This could help to check auth in simple way.
I'm coding an authentication with react-router v4 and I'm using the PrivateRoute with render props, like the documentation: Redirects (Auth)
What I'm trying to do is: Whenever the user navigates to a route, I want to dispatch an action to make a request to the backend to verify if he's logged in.
Like this:
// App.js
class App extends Component {
checkAuth = () => {
const { dispatch, } = this.props;
// callback to dispatch
}
render() {
const props = this.props;
return (
<Router>
<div className="App">
<Switch>
<Route exact path="/" component={Login} />
<PrivateRoute
exact
path="/dashboard"
component={Dashboard}
checkIsLoggedIn={this.checkAuth}
/>
{/* ... other private routes here */}
</Switch>
</div>
</Router>
);
}
In PrivateRoute.js I'm listening the route to check if it changes, but when a route changes, this function is called too many times, and that's a problem to dispatch an action to make a request.
// PrivateRoute.js
const PrivateRoute = ({ component: Component, auth, checkIsLoggedIn, ...rest }) => (
<Route
{...rest}
render={props => {
props.history.listen((location, action) => {
if (checkIsLoggedIn) {
// Here I check if the route changed, but it render too many times to make a request
checkIsLoggedIn(); // here is the callback props
}
});
if (auth.login.isLoggedIn) {
return <Component {...props} />;
} else {
return <Redirect to={{ pathname: "/login", state: { from: props.location } }} />
}
}
}
/>
);
I need a help to figure it out a good way to call the backend whenever the route changes.
Creating a Higher Order Component (HOC) is a very clean way to do this. This way, you won't need to create a separate PrivateRoute component, and it would take only one line of change to convert any Component from public to protected, or vice versa.
Something like this should work:
import React from 'react';
import { Redirect } from "react-router-dom";
export function withAuth(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
isUserLoggedIn: false,
isLoading: true
};
}
componentDidMount() {
// Check for authentication when the component is mounted
this.checkAuthentication();
}
checkAuthentication() {
// Put some logic here to check authentication
// You can make a server call if you wish,
// but it will be faster if you read the logged-in state
// from cookies or something.
// Making a server call before every protected component,
// will be very expensive, and will be a poor user experience.
this.setState({
isUserLoggedIn: true, // Set to true or false depending upon the result of your auth check logic
isLoading: false
});
}
render() {
// Optionally, you can add logic here to show a common loading animation,
// or anything really, while the component checks for auth status.
// You can also return null, if you don't want any special handling here.
if (this.state.isLoading) return (<LoadingAnimation />);
// This part will load your component if user is logged in,
// else it will redirect to the login route
if (this.state.isUserLoggedIn) {
return <WrappedComponent authData={this.state} {...this.props} />;
} else {
return <Redirect to={{ pathname: "/login", state: { from: props.location } }} />;
}
}
}
}
Once you have that component in place, all you need to do is use the HOC in any component that you wish to have protected. For example, in your case, the export line in your Dashboard file would be something like this:
/* Dashboard.js */
class Dashboard extends React.Component { ... }
export default withAuth(Dashboard);
and in your App, you can use a simple Route component:
<Route exact path='/dashboard' component={Dashboard} />
Your App does not need to care about which routes are protected, and which ones aren't. In fact, only the actual components need to know that they are protected.
Hope this helps. Cheers! :)
I'm developing a universal react application using redux. I use react-router v3.
I want to show a progress bar "BEFORE" going to next route (next route is fetching data from API).
for example imagine I am in "Home Page" and I want go to "Submit Page". when I click on the Submit Link (react-router Link) first show a progress bar in "Home Page" and wait for Submit page data fetching and then go to "Submit Page".
My React Routes:
<Route component={App}>
<Route path={HomingRoutes.HomePage} component={HomePage}/>
<Route path={HomingRoutes.SubmitPage} component={SubmitPage}/>
<Route path={HomingRoutes.SearchPage} component={SearchPage}/>
<Route path={`${HomingRoutes.DealsPage}`} component={DealsPage}/>
<Route path={`${HomingRoutes.DealPage}/:id(/:title)`} component={DealPage}/>
<Route path={`${HomingRoutes.Detail}/:id(/:title)`} component={DetailPage}/>
<Route path="*" component={NoMatch}/>
</Route>
in Home Page :
<Link to "/Submit" >Submit</Link>
My Submit page Container code is :
class SubmitContainer extends React.Component {
static readyOnActions(dispatch) {
return Promise.all([
dispatch(SubmitActions.fetchSubmitInitialData()),
]);
}
componentDidMount() {
this.props.fetchSubmitInitialData();
}
}
"fetchSubmitInitialData" is an action creator that fetch data from API.
One solution would be to pass a placeholder component as props to your SubmitPage that will render only when data is fetching.
So you can use something like:
class SubmitContainer extends React.Component {
state = {
loading: true
progress: 0,
}
componentDidMount() {
// fetch some data and update the state
// consider updating the progress more often
this.props.fetchSubmitInitialData()
.then(() => {
this.setState({ loading: false, progress: 100 })
})
}
render() {
const Placeholder = this.props.placeholder
// Show the placeholder when loading
if (this.state.loading) {
return <Placeholder loading progress={this.state.progress} />
}
// Otherwise render your component with the data
return <SubmitPage data={/*..*/}>
}
}
And finally pass you could use the component HomePage as placeholder like this:
<Route path={HomingRoutes.HomePage} component={HomePage}/>
<Route path={HomingRoutes.SubmitPage} render={(props) => (
<SubmitContainer {...props} placeholder={HomePage} />
)}/>
Here I use the render props with React router v4. But I'm sure there is an equivalent for the version 3
Now HomePage will render during data fetching and can use the props loading and progress to show a spinner or something
You can add onEnter hook into your router and add onEnter.js inside your SubmitContainer folder and move fetchSubmitInitialData to the onEnter.js then import your store here and dispatch it. the implementation might looks like this:
Your React-Route
import { onEnterSubmitPage } from './your onEnter path/onEnter'
<Route component={App}>
<Route path={HomingRoutes.HomePage} component={HomePage}/>
<Route path={HomingRoutes.SubmitPage} component={SubmitPage} onEnter={onEnterSubmitPage}/>
<Route path={HomingRoutes.SearchPage} component={SearchPage}/>
<Route path={`${HomingRoutes.DealsPage}`} component={DealsPage}/>
<Route path={`${HomingRoutes.DealPage}/:id(/:title)`} component={DealPage}/>
<Route path={`${HomingRoutes.Detail}/:id(/:title)`} component={DetailPage}/>
<Route path="*" component={NoMatch}/>
</Route>
create onEnter.js file in SubmitPage container:
/**
* Import dependencies and action creators
*/
import { store } from '../../index'
import { fetchSubmitInitialData } from './actions'
/**
* Define onEnter function
*/
export function onEnterSubmitPage() {
store.dispatch(fetchSubmitInitialData())
}
then we can integrate state for progress bar into redux too.
actions.js
/** Import all dependencies here **/
import axios from 'axios'
import { FETCH_SUBMIT_INITIAL_DATA, IS_FETCHING_INITIAL_DATA } from './constants'
export function fetchSubmitInitialData() {
/** this dispatch is from middleware **/
return (dispatch) => {
/** this will set progress bar to true **/
dispatch(fetchSubmitInitialData(true))
/** Your fetching action here, this will depend on your configuration **/
axios.get(`url`, {{ headers: `bearer //your token`}})
.then( (response) => {
dispatch(fetchSubmitInitialData(false))
})
}
}
export function isFetchInitialData(status) {
return {
type: IS_FETCHING_INITIAL_DATA,
status
}
}
so there is no need to fetch the data inside SubmitPage container.
One solution would be to pass a placeholder component as props to your SubmitPage that will render only when data is fetching.
So you can use something like:
class SubmitContainer extends React.Component {
render() {
/** this come from your reducer **/
const { isFetching, submitInitialData } = this.props
// Show the placeholder when loading
if (isFetching) {
return <Loader />
}
// Otherwise render your component
return <SubmitPage data={/*..*/}>
}
}
// Map state to props
const mapStatetoProps = ({ app }) => {
isFetching: //,
submitInitialData: //
}
export default connect(mapStatetoProps, null)(SubmitContainer)