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 ( ...
}
Related
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)() ?? {};
};
I have a route like
<Route path="/search/:slug"><SearchPage /></Route>
in my React Router. SearchPage is a component that uses the useParams() hook provided by react-router-dom.
// SearchPage.js
import React from 'react';
import { useParams } from 'react-router-dom';
export function SearchPage(props) {
const { slug } = useParams();
console.log(slug)
// ...do other stuff
}
However, when I navigate to /search/foo#bar, the SearchPage component only receives foo in the slug. It is possible for my component to receive the full foo#bar, or, better yet, foo and bar separately?
You need to use useLocation hook and get the hash from there. First step is to import useLocation from react-router-dom:
import useLocation from "react-router-dom";
Inside the component function, use this:
const { hash } = useLocation();
From the docs:
useLocation
The useLocation hook returns the location object that represents the current URL. You can think about it like a useState that returns a new location whenever the URL changes.
This could be really useful e.g. in a situation where you would like to trigger a new “page view” event using your web analytics tool whenever a new page loads
In a URL like this: "/myUrl/products/product12?myQuery=12#myHash"
The UseParams will return everything up to the query e.g "/myUrl/products/product12" but not the query "?myQuery=12#myHash".
React has a hook for this "useLocation". Use Location will give you an object with 4 things but only 3 that you need to worry about for this example, (
"pathname": "/myUrl/products/product12",
"search": "myQuery=12",
"hash": "myHash")
import { useLocation } from 'react-router-dom';
const QueryDetails = () => {
const location = useLocation();
const myQuery = location.search;
return (
<div className='container mt-2'>
<p>{myQuery}</p>
</div>
);
}
export default QueryDetails;
On my own code I tried to use react-router's useHistory by adding it to the imports:
import {BrowserRouter as Router, Link, Route, Switch, useHistory} from "react-router-dom";
and then defining a variable with it on my App() function:
let history = useHistory();
When I do that, I get the error:
TypeError: useContext(...) is undefined
coming from react-router's hooks.js, the specific line is:
return useContext(Context).history;
The whole file looks like this:
import React from "react";
import invariant from "tiny-invariant";
import Context from "./RouterContext.js";
import matchPath from "./matchPath.js";
const useContext = React.useContext;
export function useHistory() {
if (__DEV__) {
invariant(
typeof useContext === "function",
"You must use React >= 16.8 in order to use useHistory()"
);
}
return useContext(Context).history;
}
export function useLocation() {
if (__DEV__) {
invariant(
typeof useContext === "function",
"You must use React >= 16.8 in order to use useLocation()"
);
}
return useContext(Context).location;
}
export function useParams() {
if (__DEV__) {
invariant(
typeof useContext === "function",
"You must use React >= 16.8 in order to use useParams()"
);
}
const match = useContext(Context).match;
return match ? match.params : {};
}
export function useRouteMatch(path) {
if (__DEV__) {
invariant(
typeof useContext === "function",
"You must use React >= 16.8 in order to use useRouteMatch()"
);
}
return path
? matchPath(useLocation().pathname, path)
: useContext(Context).match;
}
Some more context:
I tried accessing React.useContext on my own code and it is defined and it is a function.
Any ideas what might be going on here?
I think that you should wrap your App in index.js with the BrowserRouter (as Router) and then in your App you define the Switch and Routes. Because you cannot use useHistory or useLocation in the same file where you use BrowserRouter.
So, use BrowserRouter wrapper one level up.
That happens because you need to wrap your component in a Router element. e.g:
import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
// import MyComponent
const history = createBrowserHistory();
const MyApp = ()=> (
<Router {...{ history }}>
<MyComponent />
</Router>
);
export default MyApp;
It's definately not the case for this specific issue, but maybe someone will end up with a similar issue as I did.
For me the solution was to import hooks from react-router-dom instead of react-router package.
In my case I was trying to implement custom hook in another local package and it turned out that package didn't have react-router nor react-router-dom as dependency.
Error message was exactly the same and no other error from IDE or compiler.
Took me quite a while to figure this out. So the bottom line is: double-check your dependencies.
I've run into a similar issue with useRouteMatch(). I'm not sure if the cause is the same. I receive the error Cannot read property 'match' of undefined from line useContext(Context).match; when calling useRouteMatch() in my tests.
Option 1:
One of the ways the return from useContext can be undefined is if the Context supplied to useContext doesn't include any data. For example if you remove value={{ name: "Pupeno" }} from https://codesandbox.io/s/react-hooks-usecontext-example-wv76d?file=/src/index.js:320-347 you'll see a similar error.
There could be a similar bug in react-router-dom that allows the Context to be empty when it's called from these hooks.
Option 2:
It's hard to tell without looking at your code. It could also be something like https://github.com/ReactTraining/react-router/issues/7332
I am using the new useHistory hook of React Router, which came out a few weeks ago. My React-router version is 5.1.2. My React is at version 16.10.1. You can find my code at the bottom.
Yet when I import the new useHistory from react-router, I get this error:
Uncaught TypeError: Cannot read property 'history' of undefined
which is caused by this line in React-router
function useHistory() {
if (process.env.NODE_ENV !== "production") {
!(typeof useContext === "function") ? process.env.NODE_ENV !== "production" ? invariant(false, "You must use React >= 16.8 in order to use useHistory()") : invariant(false) : void 0;
}
return useContext(context).history; <---------------- ERROR IS ON THIS LINE !!!!!!!!!!!!!!!!!
}
Since it is related to useContext and perhaps a conflict with context is at fault, I tried completely removing all calls to useContext, creating the provider, etc. However, that did nothing. Tried with React v16.8; same thing.
I have no idea what could be causing this, as every other feature of React router works fine.
***Note that the same thing happens when calling the other React router hooks, such as useLocation or useParams.
Has anyone else encountered this? Any ideas to what may cause this?
Any help would be greatly appreciated, as I found nothing on the web related to this issue.
import React, {useEffect, useContext} from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import { Switch, useHistory } from 'react-router'
import { useTranslation } from 'react-i18next';
import lazyLoader from 'CommonApp/components/misc/lazyLoader';
import {AppContext} from 'CommonApp/context/context';
export default function App(props) {
const { i18n } = useTranslation();
const { language } = useContext(AppContext);
let history = useHistory();
useEffect(() => {
i18n.changeLanguage(language);
}, []);
return(
<Router>
<Route path="/">
<div className={testClass}>HEADER</div>
</Route>
</Router>
)
}
It's because the react-router context isn't set in that component. Since its the <Router> component that sets the context you could use useHistory in a sub-component, but not in that one.
Here is a very basic strategy for solving this issue:
const AppWrapper = () => {
return (
<Router> // Set context
<App /> // Now App has access to context
</Router>
)
}
const App = () => {
let history = useHistory(); // Works!
...
// Render routes in this component
Then just be sure to use the "wrapper" component instead of App directly.
Note to other people that run into this problem and already have wrapped the component with Router component. Make sure that Router and the useHistory hook are imported from the same package. The same error can be thrown when one of them are imported from react-router and the other one from react-router-dom and the package versions of those packages don't match. Don't use both of them, read about the difference here.
useHistory won't work in the component where you have your Routes because the context which is needed for useHistory is not yet set.
useHistory will work on any child component or components which you have declared in your Router but it won't work on Router's parent component or Router component itself.
The solution is:
in the Main (father) component
import { BrowserRouter } from "react-router-dom";
<BrowserRouter><App /></BrowserRouter>
in the child component (App)
import { withRouter } from "react-router-dom";
function App(props) {
const { i18n } = useTranslation();
const { language } = useContext(AppContext);
let history = useHistory();
useEffect(() => {
i18n.changeLanguage(language);
}, []);
return(
<Route path="/">
<div className={testClass}>HEADER</div>
</Route>
)
}
export default withRouter(App);
I updated my react-router-dom from 5.0.0 to ^5.1.2 and it's been solved. You may notice that the useHistory is in a sub-component.
Use BrowserRouter.
import {
BrowserRouter as Router,
Route,
Switch,
} from 'react-router-dom';
If you use Router, then you need to specify a history for it:
import {
Router,
Route,
Switch,
} from 'react-router-dom';
// Ensure you destructure the createBrowserHistory object
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
return (
<Router history={history} >
...
</Router>
);
In a short, you should move const history = useHistory(); to your sub-component
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