How to properly use useHistory () from react-router-dom? - javascript

How to use useHistory() correctly? I can't make the transition from one react component to another.
According to the instructions from the React documentation and also here on Stack Overflow, I cannot make the transition from App.js to MyComponent.js.
For example - I am trying
/* **App.js ** */
/* Import modules */
import React from 'react';
import { useHistory } from 'react-router-dom'; // version 5.2.0
function App()
{
let history = useHistory ();
const handleClick = () => {
history.push ('./pages/MyComponent');
}
return (
<div className="App">
<button onClick={handleClick}>Next page ==></button>
</div>
);
}
I also tested this example, but the output throws the following error when the button is pressed:
TypeError: Cannot read property 'push' of undefined
Does something seem to be leaking to me or is there a mistake on Babel's side?
Project react structure:
+ Root/
+ src/
- App.js
- index.js
+ pages/
- MyComponent.js

This has changed in v6, useHistory is now useNavigate and we can use it as follows:
instead of:
const history = useHistory()
history.push('/')
we now use:
const navigate = useNavigate()
navigate('/')

You can't just use the useHistory hook to redirect to another page.
You need to properly set up your application in order to use React Router. Look at their examples starting from this https://reactrouter.com/web/example/basic
You need to wrap your entire application with <BrowserRouter /> which will give the history object you are looking for through the hook.
By the way, you don't give a relative file path to history.push as an argument, you must give a valid route that you typically setup using <Route /> component

Using history.replace('/<route-name>') also works.

you need to use it with react-router-dom. set your router config and then push it to that path. you can get more information by looking at documentation.
https://reactrouter.com/web/example/route-config
do not forget to set your switch components and your exact for root path.

Using React 17.0>, this works for me:
import { useHistory } from "react-router-dom";
const history = useHistory();
history.push("/home");
I've reached too much to find this correctly use of the useHistory function.
Try and answer this post for feedback.

When you are applying history.push, is that your path name? history.push('/pathname') is the process I guess.
You can see here: https://reactrouter.com/web/api/Hooks

Related

how to listen for route change in react-router-dom v6

am trying to migrate the old react router dom code to v6 and I want to know how to listen for route change, I am now using useHistory
const history = useHistory()
//then
history.listen(...)
I did read the new docs and I did find that useHistory was changed to useNavigate
const navigate = useNavigate()
//then
navigate.listen(...) // listen is not a function
can you please help me find a way to listen to the route change in v6
// This is a React Router v6 app
import { useNavigate } from "react-router-dom";
function App() {
let navigate = useNavigate();
function handleClick() {
navigate("/home");
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);
}
From documentation (https://reactrouter.com/en/main/hooks/use-location), use this hook
let location = useLocation();
React.useEffect(() => {
ga('send', 'pageview');
}, [location]);
The navigate function is a function, not an object like the older react-router-dom version 5's history object.
You can still create a custom history object but you'll need to create a custom router to use it. This allows you to import your history object and create listeners.
Create a custom router example, use one of the higher-level routers as an example for how they manage the location and state, i.e. BrowserRouter:
const CustomRouter = ({ history, ...props }) => {
const [state, setState] = useState({
action: history.action,
location: history.location
});
useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
{...props}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
};
In your code create the custom history object for use by your new custom router and other components. Ensure you have history#5 installed as a project dependency. This is the same version used by RRDv6. If you need to install it run npm i history#5 to add it to the project's dependencies.
const history = createBrowserHistory();
export default history;
Use your router and pass your history object to it.
import CustomRouter from '../CustomRouter';
import history from '../myHistory';
...
<CustomRouter history={history}>
....
</CustomRouter>
In a component you want to listen to location changes on, import your history object and invoke the listen callback as you did previously.
import history from '../myHistory';
...
useEffect(() => {
const unlisten = history.listen((location, action) => {
// ... logic
});
return unlisten;
}, []);
If you want, you may be able to also create your own custom useHistory hook that simply returns your history object.
Update
react-router-dom has started exporting a HistoryRouter for a use case like this. Instead of importing the low-level Router and implementing the internal logic you import unstable_HistoryRouter as HistoryRouter and pass your custom history object (memory, hash, etc).
import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
import history from "../myHistory";
...
<HistoryRouter history={history}>
....
</HistoryRouter>
Notes on RRDv6.4+
If you are using RRDv6.4+ and not using the Data routers the good-ish news is that unstable_HistoryRouter is still being exported through at least RRDv6.8.0. You can follow along the filed issue in the repo here.
If you are using the Data routers then the new "unstable" method is to use an attached navigate function from the router object directly.
Example:
import { createBrowserRouter } from 'react-router-dom';
// If you need to navigate externally, instead of history.push you can do:
router.navigate('/path');
// And instead of history.replace you can do:
router.navigate('/path', { replace: true });
// And instead of history.listen you can:
router.subscribe((state) => console.log('new state', state));
I've had mixed results with using the history.listen solution between versions 6.4 and 6.8, so probably best to keep an eye on the linked issue for whatever the RRD maintainers say is the current "unstable" method of accessing the "history".
To add to the accepted answer (can't comment, not enough rep points), subscribing to the history through a useEffect with location.pathname in the dependency array won't work if the navigation unmounts the component you're attempting to call the useEffect from.
If you need to react to a change in the route due to back button specifically:
In react-router-dom v6.8.0 or even earlier, trying to attach a listener to the history, will throw an error: A history only accepts one active listener.
I learnt that react-router-dom seems to introduce a lot of changes between the minor versions as well, so you should take words like unsafe and unstable , like in unstable_HistoryRouter especially serious. They will break sooner or later, if you're not very lucky.
In my case I had to upgrade to get the reintroduced optional route params, and the UNSAFE_NavigationContext my former colleague decided to use, didn't work anymore.
So here's a high level approach, that allows you to listen to the actions on the Router's history stack, without attaching another listener to the router yourself. Which is fine, as it already has one listener by default, and it's just not exposed, but the actions derived from it are, which is enough.
In the following example we are reacting to changes in location and for each change, we check if it was due to a POP action, thats e.g. triggered when the browser's back button is used, and then execute whatever..
import { useEffect } from "react";
import {
Location,
NavigationType,
useLocation,
useNavigationType,
} from "react-router-dom";
export const useBackListener = (callback: () => void) => {
const location: Location = useLocation();
const navType: NavigationType = useNavigationType();
useEffect(() => {
if (navType === "POP" && location.key !== "default") {
if (someCondition === true) callback();
else {
doSomethingElse();
}
}
}, [location]);
};

Cannot get history.push(url) to re-render page

This comes up often, but no solution appears to work and I feel like I've exhausted every option I can think of, so I'm turning to here, believing this may be a slightly different scenario and not a duplicate. If there's an existing solution that works, I'm happy to be shown otherwise.
In my last create-react-app project, I had no problem rendering new pages with Router and useHistory. Using the exact same set up, I've run into the issue where history.push(url) changes the url in the address bar but the page doesn't re-render.
I'm using:
React v17.0.2
react-router-dom v5.3.0
history v5.0.1 (also attempted
on v4.10.1)
App.js
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { Page } from './stories/Page';
import './App.css';
function App() {
return (
<Router>
<Switch>
<Route exact path="/">
<Page homepage={true} />
</Route>
<Route path="/project/([a-zA-Z0-9]+)/([a-z\-0-9]+)/([a-z\-0-9]+)/" render={urlprops =>
<Page projectID={urlprops.match.params[0]} homepage={false} />
} />
<Route path="/project/([a-zA-Z0-9]+)/([a-z\-0-9]+)/" render={urlprops =>
<Page projectID={urlprops.match.params[0]} homepage={false} />
} />
</Switch>
</Router>
);
}
export default App;
component-page.js
import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import './menuitem.css';
export const Component = ({ url, ...props }) => {
let history = useHistory();
function handleItemClick(e) {
history.push(url); // <- calls fine, tested with logging to console
}
...
I've seen older solutions suggest passing history props through Router but in my last app I was able to use the set up above, and any level nested subcomponent would be able to call the useHistory hook and use push if they were set up like component-page.js above. Therefore I haven't shown the components between App.js and component-page.js on the belief that this should be irrelevant to useHistory functioning correctly.
Possibly relevant:
If I enter the URLS in the address bar and hit return, Router renders the correct components, suggesting Router and Switch work fine with a freshly loaded page.
I have React dev tools set up to blink on
components when they re-render. After history.push(url) is called all
components blink, though none re-render (visually, at least).
Downgrading history to 4.10.1 seemed to fix a lot
of the recent reports of this issue for others. I had no such luck. Apparently there was/is a bug with react-router-dom v5+ which meant history.push was not working with history v5+.
I also placed a simple button component in the Page component that fired a request to history.push("string url here") with the same issue. It calls, address bar changes, page doesn't re-render. In my mind, this rules out it being some sort of nesting issue, which I didn't believe it was anyway.
Update
Checking history in console after calling history.push('url string') shows the following under the location property. This looks like it's acting as intended (I'm only expecting pathname to match the url I tried to point to).
{
"pathname": "url string",
"search": "",
"hash": "",
"key": "ctks7v"
}
Update 2
Possible progress. In my Page component I am calling fetch in a hook to catch any update, like so:
useEffect(() => {
apiFetchProject(projectID);
}, []);
I'm now wondering if this is my problem? That this hook isn't seeing the url as a change that requires firing apiFetchProject(projectID). The reason I say this is that console calls confirm this hook isn't being called here after my history.push, but a console call to the Page component is happening. I've tried calling the hook with any change in props.location but this comes back as undefined each time, which means the hook isn't called.
Update 3
If, on Page, I use import { useLocation } from "react-router-dom"; with let location = useLocation(); and let a hook look for a change in location, Page now successfully calls fetch after a history.push call. But something doesn't seem right about having to do this too? I feel I would have seen it as a standard instruction.
I'm having to add it to every component to see re-rendering of anything. The fetch in Page is stored as a state and passed down as a prop to subcomponents. A change of this state should automatically re-render all subcomponents that are passed it, as happens when the page is loaded.
instead of the url, put the path ('/home')

React Router v6 useNavigate() doesn't navigate if replacing last element in path

I have a react component with the following function
const handleNavigate = (clientId) => {
console.log(clientId)
navigate(`/dashboard/clients/${clientId}`)
}
The console.log() is showing the ID I want to append to use in the navigate function.
AND
The URL in the browser is updating.
But the page does not change.
This works to navigate from /dashboard/clients to /dashboard/clients/foo
but it does not work to navigate from /dashboard/clients/foo to /dashboard/clients/bar
The clientId is passed into the card like so...
const CompanyCard = (props) => {
const { client, showWatchlist, removeDisabled, showRemove, removeType } = props
...
}
then in the card
<CardActionArea
onClick={() => handleNavigate(client._id)}
...
Any ideas?
Thanks
UPDATE
After reading up from #redapollos suggestion I tried Outlet and the
useRoutes methods... neither worked.
import { useRoutes } from 'react-router-dom'
// then in the routes...
{ path: 'clientdetail/:id', element: <ClientDetail /> },
{ path: 'clientdetail/', element: <ClientDetail /> },
This might be due to using the useRoutes hook but I am still working on it.
Another question here on SO that might get an answer sooner -
https://stackoverflow.com/a/70009104/13078911
I just read in React Router Dom Docs v6 this solution:
import { useNavigate } from 'react-router-dom';
...
const navigate = useNavigate();
...
<Button onClick={() => navigate('../user', { replace: true })}>Register</Button>
So, basically I added ../ before the route and the replace: true.
Reference: https://reactrouter.com/docs/en/v6/hooks/use-navigate
It worked for me, hope it works for u! (:
Workable solution!
navigate('/url')
navigate(0)
After replacing the url, manually calling navigate(0) will refresh the page automatically!
This will work in a situation like
navigate from /same_path/foo to /same_path/bar.
Be aware of unintended page referesh behavior:
For a normal situation like navigate from /home to /same_path/bar, navigate(0) will cause page to refresh even after page has finished rendering. And to prevent that, you can take a look at this question.
More info:
useNavigate docs
How do I reload a page with react-router?
Try replacing the URL instead of adding a new one. when you are going from
/dashboard/clients to /dashboard/clients/foo you are going from a parent to a child, your URL has everything plus /foo. But, when you are going from /dashboard/clients/foo to /dashboard/clients/bar you are navigating to a sibling /foo to /bar that might be causing the issue. try to replace the value like navigate(/dashboard/clients/ba, {replace: true}) here is example of how to use this in general. use it for more information. https://www.digitalocean.com/community/tutorials/react-react-router-v6
I had this same issue and my code was fine, however, I found out at this post that optional params aren't supported in v6.
https://github.com/remix-run/react-router/issues/7285
I had:
<Routes>
<Route path="list/:id?" element={<SystemList />} />
</Routes>
and had to change it to:
<Routes>
<Route path="list/:id" element={<SystemList />} />
<Route path="list/" element={<SystemList />} />
</Routes>
I'm hoping they support it in the future but as of v6.0.2 they do not.
Maybe try the component Navigate:
<Navigate to={<your_path>}/>
This might be a little late but I had the same issue and you simply just have to trigger a re-render for your component. You can do this by adding a useEffect hook within your component you have assigned /dashboard/clients/:clientId to with a dependency of the param you want to update.
It should look something like this:
import React, { useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom';
const ClientDashboard = () => {
const params = useParams()
const navigate = useNavigate()
useEffect(() => {
//do something with new id
},[params.clientId])
const handleNavigate = (clientId) => {
navigate(`/dashboard/clients/${clientId}`)
}
.....
}
Your page should now update with the new param
I think this is a better work around on this one.
import React from 'react'
import { useHistory } from 'react-router-dom'
...
const history = useHistory()
const handleNavigate = (clientId) => {
console.log(clientId)
history.push(`/dashboard/clients/${clientId}`)
}
Make sure your app is wrapped in a BrowserRouter from react-router-dom
Try adding window-location as key prop to the element in route
ie
<Route path=":id" element={ <Hello key={window.location.pathname} } />
Based on this link https://github.com/remix-run/react-router/issues/8245 I could solve this issue.
I guess that as nothing has been changed the screen is not updated. As soon as I added I useEffect on the target route component and passed the lastelement as a parameter it worked.
In other words: On the target component add: Reac.useEffect(....) [lastElement].

How can i use router.push with shallow routing in next js?

I am beginner in next js.I know how next js routing works but shallow routing not working as i expected in my project. Please share a helpful resources about it except official documentation
I would take a look at this post:
import Router from 'next/router' is it ok?
Here is a similar question about this topic. Otherwise I would really suggest reading the docs of Next.js
https://nextjs.org/docs/api-reference/next/router
You can try this way.
import { withRouter } from "next/router";
import Router from "next/router";
const ComponentName= ({ router }) => {
//.......
Router.replace(`/`); // here will be location
}
export default withRouter(ComponentName);
if still not work, then try with uesEffect()
useEffect(() => {
// you initial function
}, [router]);

How to read the current URL in the react application

I would like read and print the current URL in the react application. Right now i am using "window.location.pathname" to read the URL but would like to know if there is a better way or some react way to read the URL
window.location.href
returns the href (URL) of the current page
window.location.hostname
returns the domain name of the web host
window.location.pathname
returns the path and filename of the current page
window.location.protocol
returns the web protocol used (http: or https:)
if you are using React Router and your component is rendered by a Route like below for example:
<Route exact path='/' component={HomeComponent}/>
that component will automatically receive three objects from Route named history , location and match respectively. by that you can find what you asked under location.pathname. more info here
if you still using react router and your component is not been rendered with Route , you need to use withRouter , which is a HOC and will give you history , location and match as props to your component. more info here
if you are not using react router you gonna need to use window.location.pathname or window.location.href or only location.pathname
If you are using react router:
const currentRoute= this.props.location.pathname
else you can get this like:
const currentRoute= window.location.pathname
href will give you complete url.
We can get it from this.props using withRouter component from react-router-dom package in the following way by adding in the class
import React from 'react';
import { connect } from 'react-redux';
import { Switch, Route, withRouter } from 'react-router-dom';
class Application extends React.PureComponent {
render() {
console.log(this.props)
this.props.location.pathname // we will get the pathname
return (
<div className='application'>
{Hi}
</div>
);
}
}
export default connect(mapStateToProps, actions)(withRouter(Application));
output:
You can use the useParams hook to access values in your URL. When creating your routes in App.jsx you will specify the name of the input variable like this:
<Route
path="/search/:searchType/:module/:category/:search/:source"
exact={true}
component={yourComponent}
/>
Then you can use the useParams hook inside your component to access the variable values.
const {searchType, module, category, search, source} = useParams();
if you using react js then use useLocation and useHistory hooks
import { useHistory ,useLocation } from 'react-router-dom';
const location = useLocation()
console.log(location.pathname)
const history = useHistory()
console.log(history.location.pathname)

Categories

Resources