This question already has an answer here:
How to run a function when user clicks the back button, in React.js?
(1 answer)
Closed 5 months ago.
I'm new to React, so I'm sure I'm not understanding the use cases for useLocation - like what it is good for and what it is not intended for.
I'd like to have a method that a specific component can be aware of any location change included those from pushState. Note: I'm converting an Anuglar JS 1.0 code base that just used all query info in the hash. I'd like to use pushState browser feature in this rewrite.
Sample code below (I just have it as the single component in a new React app component:
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
const RandLocation: React.FC = () => {
const location = useLocation();
useEffect(() => {
console.log('location: ', location);
}, [location]);
return (
<div>
<button
onClick={() => {const r = Math.random(); window.history.pushState({'rnd': r }, '', '/?rnd=' + r)}}>
Click Me</button>
<br/>
</div>
)
}
export default RandLocation;
I only see the useEffect run on load, and if I move forward or back using the browser buttons. But not when I click the "Click Me" button. What am I missing? Id like to keep this "awareness of location" as simple as possible within the React frontend code. Like is there a technique that works in apps regardless of if you have React Router routes defined?
I am using React version 17.0.2 and react-router-dom version 6.2.2
I think because the window.history.pushState call is outside of React's state management it react-router won't be aware of it. There used to be a way to listen for these events, but I'm not sure something equivalent exist in React Router 6.
You could use the useNavigate hook. Maybe something like:
import React, { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
const RandLocation = () => {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
console.log("location: ", location);
}, [location]);
return (
<div>
<button
onClick={() => {
const r = Math.random();
//window.history.pushState({ rnd: r }, "", "/?rnd=" + r);
navigate("/?rnd=" + r, { state: { rnd: r } });
}}
>
Click Me
</button>
<br />
</div>
);
};
export default RandLocation;
One issue with this approach, is you'd have to set up a default route to catch anything that no route is defined for like this:
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="*" element={<WhereYouWantDefaultRoutesToGoTo />} />
</Routes>
</BrowserRouter>
You might also want to take a look at: https://stackoverflow.com/a/70095819/122201
Related
This question already has answers here:
How to pass data from a page to another page using react router
(5 answers)
Closed 4 months ago.
I'm teaching myself React (using v18.1.0) and I'm struggling to understand why I am getting an undefined object off the properties I'm passing to a component through a NavLink using react-router-dom v.6.3.0.
I have a component that has a NavLink that is created when a certain variable (let's call it "var1") is not null, like so:
[...]
{
var1 != null
?
<NavLink className="Menu-Button" to={{pathname : "/Component1"}} state = {{ props : var1 }}>ButtonContent</NavLink>
: <p/>
}
[...]
The component being routed to (let's call it "Component1") looks like this:
import React from 'react';
import {useLocation} from 'react-router-dom';
const Component1= () => {
const location = useLocation();
const { props } = location;
console.log(props);
return(
[...]
)
};
export default Component1;
The output of that console.log is undefined. I've also tried using props.location instead of useLocation(), but I get the same issue. Where am I going wrong?
EDIT:
Including route config in App.js as requested by #Nick Vu:
N.B. Toolbar is a component that acts as a header / navigator
[all the imports]
const App = () => {
return (
<BrowserRouter>
<Toolbar />
<Routes>
[all the existing routes that work fine]
<Route exact path='/Component1' element={<Component1 /> } />
</Routes>
</BrowserRouter>
);
}
export default App;
Your NavLink seems good, but according to your setup, in Component1, you should access props values from NavLink by state.
import React from 'react';
import {useLocation} from 'react-router-dom';
const Component1= () => {
const location = useLocation();
const props = location?.state?.props; //use `?` to avoid errors from missing states
console.log(props)
return(
[...]
)
};
export default Component1;
Sandbox link
This was embarassing but, changing
state = {{ props : var1 }}
to
state={{props : var1}}
has resolved the issue.
This question already has answers here:
How to pass params into link using React router v6?
(5 answers)
Closed last year.
Before, you would need to wrap something like this export default withRouter(name of component) and now it is different in react-router v6.
I have this button where once you clicked this, it will take you to another page including the value inside my tableMeta.rowData[0].
I tried using navigate = useNavigate(), but I do not know how to pass the value of tableMeta.rowData[0]
{
name: "Edit",
options: {
customBodyRender: (value, tableMeta, updateValue) => {
return (
<Button
color="success"
onClick={
() => navigate("/edit-products")
// console.log(tableMeta.rowData[0])
}
>
Edit
</Button>
);
},
},
},
In React-router v6, how can I do this similar to a withRouter before?
It is deprecated. You can recreate it using the hooks version:
import React from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
export function withRouter<ComponentProps>(Component: React.FunctionComponent<ComponentProps>) {
function ComponentWithRouterProp(props: ComponentProps) {
const location = useLocation();
const navigate = useNavigate();
const params = useParams();
return <Component {...props} router={{ location, navigate, params }} />;
}
return ComponentWithRouterProp;
}
For more details check this out
I'm trying to make the page for the following route:
/password?token=479wasc8-8ffe-47a6-fatw-624e9d2c323a&user=e238bc4c-cf79-4cc3-b4a5-8fe7ewrta54a9w8a5
My solution to that initially was like the following:
<Route exact path='/password?token=:token&user=:user' component={Password}/>
But I guess I'm missing something important here. I tried various sources, but didn't find anything close to my problem.
The Password component can make use of the useLocation hook to read the query string:
<Route path='/password' component={Password} />
import { useLocation } from 'react-router-dom'
const useQuery = () => {
return new URLSearchParams(useLocation().search)
}
const Password = () => {
const query = useQuery()
return (
<div>
<p>{query.get('token')}</p>
<p>{query.get('user')}</p>
</div>
)
}
Here's the example on the react router website
I have some issue with React Router useLocation and how to get the current pathname.
Here is what I'm doing :
import {BrowserRouter as Router, Switch, Route, Link} from "react-router-dom";
import {useHistory, useLocation} from 'react-router-dom'
function Home(props) {
const [isHome, setIsHome] = useState(true);
const [isContents, setIsContents] = useState(false);
const [isUserProfile, setIsUserProfile] = useState(false);
let location = useLocation();
let history = useHistory();
console.log(location)
console.log(history)
function getCurrentLocation() {
}
function logout() {
const logger = fetch('/users/logout');
logger.then(logout => {
if (logout.ok) {
props.logout()
} else {
console.log('error', logout);
}
})
}
return (
<Router>
<div className={'container-fluid h-100'}>
<div className={'row'}>
<ul className={"nav nav-tabs"}>
<li className={"nav-item"}>
<Link to={'/home'}>1</Link>
</li>
<li className={"nav-item"}>
<Link to={'/contents'}>2</Link>
</li>
<li className={"nav-item"}>
<Link to={'/user'}>3</Link>
</li>
</ul>
</div>
<Switch>
<Route exact path={'/home'}>1</Route>
<Route path={'/contents'}>2</Route>
<Route path={'/user'}>3</Route>
</Switch>
</div>
</Router>
)
};
export default Home
I want to get the currentLocationPathName in order to apply some custom style.
When I make a console.log(location) this is what I get back:
function useLocation() {
if (true) {
!(typeof useContext === "function") ? true ? Object(tiny_invariant__WEBPACK_IMPORTED_MODULE_6__["default"])(false, "You must use React >= 16.8 in order to use useLocation()") : undefined : void 0;
}
return useContext(context).location;
}
Here a screenshot for more informations:
error message
I want to better understand how to use these Hooks.
Thank you for your time.
Edit:
Thank you to #Dance2die for his answer and specially this recommendation:
And also make sure Home is used under a tree of a router (e.g.) BrowserRouter) (child, grandchild, etc)
I did not put Home under <Router></Router> tree...
Now it's working good and I can get access to the location.pathname.
useHistory and useLocation are functions so you need to invoke them.
Instead of
let location = useLocation;
let history = useHistory
Invoke the hook functions.
👇
let location = useLocation()
👇
let history = useHistory()
And also make sure Home is used under a tree of a router (e.g.) BrowserRouter) (child, grandchild, etc)
Old answer before the OP's edit.
Hooks were added in v16.8.0.
https://github.com/facebook/react/blob/master/CHANGELOG.md#react-2
So the message tells you that you can't use useLocation with your version of React, v16.3.1.
To fix the issue, you need to upgrade React to at least v16.8.0 (preferably the latest, to prepare for React Router v6 to be released hopefully early or mid this year).
How would I be able to test the router in the code below? When using React you are able to use MemoryRouter to pass initialEntries to mock a route change but I cannot find an alternative for preact-router. I looked at the Preact docs and the preact-router docs but I am unable to find a clear solution.
import 'preact/debug';
import { h, render } from 'preact';
import HomePage from './pages/homepage';
import Router from 'preact-router';
import AsyncRoute from 'preact-async-route';
import './styles/index.scss';
const App = () => (
<Router>
<HomePage path="/" />
<AsyncRoute
path="/admin"
getComponent={ () => import('./pages/admin').then(module => module.default) }
/>
</Router>
);
export default App;
This is a little old, but I figured I would share what I found.
The first and quickest thing to do is to just use the route function in preact-router.
import { render, route } from 'preact-router';
import App from './App';
describe('<App/>', () => {
it('renders admin', async () => {
const { container, findByText } = render(<App/>);
// Go to admin page
route('/admin');
// Wait for page to load since it's loaded async
await findByText(/Admin Page/);
// perform expectations.
});
});
While this works, I don't like that it relies on the brower's real history. Luckily, the <Router> component accepts a history prop of type CustomHistory. So you can use an in-memory implementation of a History API to make this happen. I think I've seen docs that suggest using the history package - however I had to make an adjustment
import { createMemoryHistory } from 'history';
class MemoryCustomHistory {
constructor(initialEntries = undefined) {
this.wrapped = createMemoryHistory({initialEntries});
}
get location() {
return this.wrapped.location;
}
// Listen APIs not quite compatible out of the box.
listen(callback) {
return this.wrapped.listen((locState) => callback(locState.location));
}
push(path) {
this.wrapped.push(path);
}
replace(path) {
this.wrapped.replace(path);
}
}
Next, update your app to accept a history property to pass to the <Router>
const App = ({history = undefined} = {}) => (
<Router history={history}>
<HomePage path="/" />
<AsyncRoute
path="/admin"
getComponent={ () => import('./pages/admin').then(module => module.default) }
/>
</Router>
);
Finally, just update the tests to wire your custom history to the app.
it('renders admin', async () => {
const history = new MemoryCustomHistory(['/admin]);
const { container, findByText } = render(<App history={history}/>);
// Wait for page to load since it's loaded async
await findByText(/Admin Page/);
// perform expectations.
});