Context
I'm working with a hybrid next.js and react-router app. Parts of the app are handled by react-router (hash-based), and parts of it by next.js router. There are common components which use hooks related to the current routing state (e.g. useLocation), which crash if the react-router provider wrapper is missing.
Problem
I would like to write a hook that returns either useLocation (from react-router-dom) or useRouter (from next.js), depending on whether it detect the react-router provider in the current context.
Then I would use this hook in common components, so that they work regardless of which context they're used in.
There is a similar solution for detecting whether to use useEffect or useLayoutEffect for SSR, called useIsomorphicLayoutEffect. I'm thinking that a similar approach could work in my case. However, feel free to suggest different solutions.
The error I'm getting is TypeError: useContext(...) is undefined. The react-router wrapper provides a context which is used by the useLocation hook. Therefore I believe a generic solution for detecting the context provider would be valid here.
Example
const fooCommonComponent = () => {
// ❌ this only works when react-router-dom provider exists in the current context
const { pathname } = useLocation();
// ❌ this only works for next.js router
const { pathname } = useRouter();
// ✅ what i want
const { pathname } = useCustomLocation();
};
const useCustomLocation = () => {
// how to implement this?
};
I'm not very familiar with the Next.js side of things, but react-router-dom#6 has an useInRouterContext hook to return true/false if the component is rendered within a RRD routing context.
useInRouterContext
The useInRouterContext hooks returns true if the component is
being rendered in the context of a <Router>, false otherwise. This
can be useful for some 3rd-party extensions that need to know if they
are being rendered in the context of a React Router app.
Here's an example implementation that works for at least the RRD side of things.
import { useInRouterContext, useLocation } from "react-router-dom";
import { useRouter } from "next/router";
const useCustomLocation = () => {
const isInRRDContext = useInRouterContext();
return (isInRRDContext ? useLocation : useRouter)() ?? {};
};
Related
By searching the internet, I finally developed a hook that allows me to detect when the user is trying to navigate to another page of the app
I'm using React Router v6, which doesn't have an easy way to do this (history and prompt have been deprecated)
I built a code sandbox where you can see it all working (it also uses Mui), you'll see my refactored version which is more understandable and uses javascript
As you can see, it has a /hooks/navigationBlocker that uses UNSAFE_NavigationContext (which was developed by the same guys that developed React Router)
import * as React from 'react';
import { UNSAFE_NavigationContext } from 'react-router-dom';
export function NavigationBlocker(navigationBlockerHandler, canShowDialogPrompt) {
const navigator = React.useContext(UNSAFE_NavigationContext).navigator;
React.useEffect(() => {
if (!canShowDialogPrompt) return;
const unblock = navigator.block((tx) => {
const autoUnblockingTx = {
...tx,
retry() {
unblock();
tx.retry();
}
};
navigationBlockerHandler(autoUnblockingTx);
});
return unblock;
});
}
I don't understand this code much, I guess it is so because I never used anything previous to react-router-dom v6
Is there a way to do the same without the use of UNSAFE_NavigationContext, I don't know why it's unsafe as its name suggests
Rafael
Code Sandbox link:
and trying to follow this article
On successful login(/auth/login), the user should be routed to the dashboard(/admin/summary). If the login is successful, I am also storing an access token.
I have a PrivateRoute component for this. The problem is that on successful login, the URL is getting updated but the component is not getting rendered.
PS: about the dashboard, this is a single page application so, the dashboard has topbar, sidebar, and the right content and altogether these things are coupled inside <AdminLayout/>. So, in my AppRouter, I have to render the <AdminLayout/> and just any one component.
All the react and redux code is included in the code sandbox.
Since in your code you create your own history object (it happens in you history.js file, when you call createBrowserHistory()) but doesn't pass it to your Router, nothing happens.
There are 2 possible solutions:
1. Don't create a history object yourself, but use useHistory hook inside your component
Working Demo
With this approach, you should remove history.push from login.actions.js (which imports history) and use history.push in Login.js (which uses useHistory hook):
// login.actions.js
...
loginService.login(userid, password, rememberPassword).then(
(userid) => {
dispatch(success(userid, password, rememberPassword));
// history.push(from); <-- commented out!
},
(error) => { ... }
);
};
...
// Login.js
function handleSubmit(e) {
...
const { from } = {
from: { pathname: "/admin/summary" }
};
history.push(from) // <-- added!
dispatch(loginActions.login(inputs, from));
...
}
useHistory exposes the history object of BrowserRouter (I think this is implied in this official blog post).
2. Create a history object yourself, but pass it to a Router component
Working Demo
This approach would require you to make several changes:
Creating the history object on your own means you become responsible to provide it to a router component, but it can't be a BrowserRouter, but the base Router component (see these Github answers: 1, 2).
Once you import Router (instead of BrowserRouter), you need to get rid of any useLocation and useHistory imports, otherwise you'll get errors.
I also had to unify the history object export and imports, so that it is exported as the default export (i.e., export default history), and it is imported as the default import (i.e., import history from "./history"; instead of import { history } from "./history")
(P.S: this approach can be seen implemented elsewhere on SO, for example here or here (the latter explicitly installs history, but it's not needed in your case).
I'm working on a simple React storefront and there is a bug with my router setup. I've provided screenshots of the relevant files and the error message. The file flow for the router is Navbar.js -> App.js -> Index.js. I haven't done any routing in a long time, so I apologize if there are details I'm leaving out or something I'm not explaining correctly. Any suggestions will help.
The "Router" component is only needed once like you have it in index.js
Navbar.js does not need the "Router" component so you can remove it. Also your import of the "Router" is wrong in Navbar.js (its correct in index.js)
import { BrowserRouter as Router } from "react-router-dom"
You need to use this.props in React class components. this.props.history.location
I believe the Router component is telling us to start listen to location. So in your App.js, start by including an import of useLocation to your react-router-dom. Then make a useEffect listen to the location so Router always can know which location you are on, and thereby determin which Route component to show.
Example:
import { useLocation } from 'react-router-dom';
function App() {
const location = useLocation();
useEffect(() => {
const currentPath = location.pathname;
const searchParams = new URLSearchParams(location.search);
}, [location]);
return ( ...
}
I need to detect if a route change has occurred so that I can change a variable to true.
I've looked through these questions:
1. https://github.com/ReactTraining/react-router/issues/3554
2. How to listen to route changes in react router v4?
3. Detect Route Change with react-router
None of them have worked for me. Is there a clear way to call a function when a route change occurs.
One way is to use the withRouter higher-order component.
Live demo (click the hyperlinks to change routes and view the results in the displayed console)
You can get access to the history object's properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md
import {withRouter} from 'react-router-dom';
class App extends Component {
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
console.log('Route change!');
}
}
render() {
return (
<div className="App">
...routes
</div>
);
}
}
export default withRouter(props => <App {...props}/>);
Another example that uses url params:
If you were changing profile routes from /profile/20 to /profile/32
And your route was defined as /profile/:userId
componentDidUpdate(prevProps) {
if (this.props.match.params.userId !== prevProps.match.params.userId) {
console.log('Route change!');
}
}
With React Hooks, it should be as simple as:
useEffect(() => {
const { pathname } = location;
console.log('New path:', pathname);
}, [location.pathname]);
By passing location.pathname in the second array argument, means you are saying to useEffect to only re-run if location.pathname changes.
Live example with code source: https://codesandbox.io/s/detect-route-path-changes-with-react-hooks-dt16i
React Router v5 now detects the route changes automatically thanks to hooks. Here's the example from the team behind it:
import { Switch, useLocation } from 'react-router'
function usePageViews() {
let location = useLocation()
useEffect(
() => {
ga.send(['pageview', location.pathname])
},
[location]
)
}
function App() {
usePageViews()
return <Switch>{/* your routes here */}</Switch>
}
This example sends a "page view" to Google Analytics (ga) every time the URL changes.
When component is specified as <Route>'s component property, React Router 4 (RR4) passes to it few additional properties: match, location and history.
Then u should use componentDidUpdate lifecycle method to compare location objects before and after update (remember ES object comparison rules). Since location objects are immutable, they will never match. Even if u navigate to the same location.
componentDidUpdate(newProps) {
if (this.props.location !== newProps.location) {
this.handleNavigation();
}
}
withRouter should be used when you need to access these properties within an arbitrary component that is not specified as a component property of any Route. Make sure to wrap your app in <BrowserRouter> since it provides all the necessary API, otherwise these methods will only work in components contained within <BrowserRouter>.
There are cases when user decides to reload the page via navigation buttons instead of dedicated interface in browsers. But comparisons like this:
this.props.location.pathname !== prevProps.location.pathname
will make it impossible.
How about tracking the length of the history object in your application state? The history object provided by react-router increases in length each time a new route is traversed. See image below.
ComponentDidMount and ComponentWillUnMount check:
React use Component-Based Architecture. So, why don't we obey this rule?
You can see DEMO.
Each page must be wrapped by an HOC, this will detect changing of page automatically.
Home
import React from "react";
import { NavLink } from "react-router-dom";
import withBase from "./withBase";
const Home = () => (
<div>
<p>Welcome Home!!!</p>
<NavLink to="/login">Go to login page</NavLink>
</div>
);
export default withBase(Home);
withBase HOC
import React from "react";
export default WrappedComponent =>
class extends React.Component {
componentDidMount() {
this.props.handleChangePage();
}
render() {
return <WrappedComponent />;
}
};
I wanted to build a Facebook login into my react/react-router/flux application.
I have a listener registered on the login event and would like to redirect the user to '/dashboard' if they are logged in. How can I do that? location.push didn't work very well, except after reloading the page completely.
React Router v3
This is what I do
var Router = require('react-router');
Router.browserHistory.push('/somepath');
React Router v4
Now we can use the <Redirect>component in React Router v4.
Rendering a <Redirect> will navigate to a new location. The new location will override the current location in the history stack, like server-side redirects.
import React, { Component } from 'react';
import { Redirect } from 'react-router';
export default class LoginComponent extends Component {
render(){
if(this.state.isLoggedIn === true){
return (<Redirect to="/your/redirect/page" />);
}else{
return (<div>Login Please</div>);
}
}
}
Documentation https://reacttraining.com/react-router/web/api/Redirect
React Router v0.13
The Router instance returned from Router.create can be passed around (or, if inside a React component, you can get it from the context object), and contains methods like transitionTo that you can use to transition to a new route.
React Router v2
Even though the question is already answered, I think it's relevant to post the solution that worked for me, since it wasn't covered in any of the solutions given here.
First, I'm using the router context on my LoginForm component
LoginForm.contextTypes = {
router: React.PropTypes.object
};
After that, I can access the router object inside my LoginForm component
handleLogin() {
this.context.router.push('/anotherroute');
}
PS: working on React-router version 2.6.0
React Router v3
Navigating Outside of Components
create your app with Router like this
// Your main file that renders a <Router>:
import { Router, browserHistory } from 'react-router'
import routes from './app/routes'
render(
<Router history={browserHistory} routes={routes} />,
mountNode
)
Somewhere like a Redux middleware or Flux action:
import { browserHistory } from 'react-router'
// Go to /some/path.
browserHistory.push('/some/path')
// Go back to previous location.
browserHistory.goBack()
react-router/tree/v3/docs
React Router v4.2.0
I am using React-16.2.0 & React-router-4.2.0
And I get solution by this code
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
I was also try by return <Redirect to='/' /> But unlucky, this not working for me.
React router v5 using hooks
These steps are for authorisation redirect. But can be used for login/logout redirection also.
The <Redirect/> accepts to prop as a string or an object. We can utilise the object to pass the redirection path after login/logout using hooks easily.
Get the pathname of url from where the <Redirect/> is called using
useLocation()
const {pathname} = useLocation()
In the to prop of <Redirect/> pass in the following object:
<Redirect to={{pathname:'/login',state: {referrer: pathname}}/>
In the Login component access the route state variable using useLocation() hook and use the useHistory() hook to redirect after successful login.
const history = useHistory();
const location = useLocation();
const login() => {
// After login success
const {state: {referrer}} = location;
history.push(referrer)
};
Check the official docs here
React Router v3
Navigating inside components
You should use withRouter decorator when it's necessary to redirect inside a component. The decorator uses context instead of you.
import {withRouter} from 'react-router'
fucntion Foo(props) {
props.router.push('/users/16');
}
export default withRouter(Foo);
withRouter(Component, [options])
A HoC (higher-order component) that wraps another component to enhance
its props with router props.
withRouterProps = {
...componentProps,
router,
params,
location,
routes
}
Pass in your component and it will return the
wrapped component.
You can explicit specify router as a prop to the wrapper component to
override the router object from context.
In your store:
data.router.transitionTo('user');
And router has:
"Route name="user" handler={User}"
User is route handler