I'm thoroughly lost and would like to ask for a recomendation on how to implement browser history inside my app.
With Router, all i have is a single component which gets assigned pages based on which page i'm on. pages and text inside app is acquired from an api, and whenever i click a button, the api gets called again.
<Router>
<Switch>
<Route to="/" component={Body} />
...
which probably doesnt even work as it should, because the Link tags are on the buttons, and they point to /page/number:
const renderPageNumbers = apiPagingSliced.map((links, index) => {
return <Link key={index} to={`/page/${links.label}`}>
<button key={index} id={links.label}
onClick={props.handleClick}
className={(links.active ? "mark-page" : "") + " " + (links.url === null ? "remove-btn" : "")}
>{links.label}
</button></Link>
}
)
i've managed to get it working so that i get "www.webpage.com/page/3" for example. But when i press back in browser, it only changes the url into previous page, doesn't do anything else. How do i implement a functional back/forward history function?
First you should add the route params example : "/:id"
<Route to="/some_page/:id" component={SomePage} />
Then import useHistory and UseParams from react-router :
import { useHistory, useParams } from "react-router-dom";
let { id } = useParams();
let history = useHistory();
<button onClick={() => history.push(`/some_page/${id}`)}> Go to page </button>
First you should add a route to the /page/(some number)
And this is done like this:
<Router>
<Switch>
<Route to="/" component={Body} />
<Route to="/page/:id" component={Page} />
...
And now in the Page component import a react router dom hook called useParams
import { useHistory } from 'react-router-dom'
const Page = () => {
const history = useHistory()
//pageID will be equal to the page number
return (
<div>
<button onClick={event => history.goBack`}>Go back!</button>
</div>
)
}
Related
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
When using React Router v5, it was possible to get the path (pattern) for that route using useRouteMatch.
const { path } = useRouteMatch();
React Router v6 offers a similar hook, useMatch; however this expects to receive the pattern you want to match against.
I would use the React Router v5 hook to generate routes by combining the current path with known params.
As an example, if I am on the product page of an eCommerce application (let's assume /en/product/:id) and there are links to related products (/en/product/1, /en/product/2...etc), I would previously have been able to do:
const MyComponent = () => {
const { path } = useRouteMatch();
return (
<div>
<Link to={generatePath(path, { id: 1 })}>Related Product 1</Link>
<Link to={generatePath(path, { id: 2 })}>Related Product 2</Link>
</div>
);
};
Since /en/product comes from an API and is not declared anywhere in the code, I would want the URLs to be updated based on the current path. If the user is on /es/producto, then the links would automatically be updated to /es/producto/1.
I have seen solutions on SO where it was suggested to use matchRoutes, however it feels highly inefficient, especially since the routes are dynamically generated from an external API.
const useCurrentPath = () => {
const location = useLocation()
const [{ route }] = matchRoutes(routes, location)
return route.path
}
I have created a small demo to illustrate how this used to work:
Code Sandbox Demo
In react-router-dom#6 there is no replacement for the v5 useRouteMatch hook. The links and routes no longer need to be concerned with the path pattern so much as they can simply use relative routes and links. Instead of trying to access a route's path pattern it can simply navigate relative to the currently matched path.
Example:
This navigates from "/route-a/2" to "/route-a/2/../1" or rather "/route-a/1".
const { pathname } = useLocation();
// navigate to sibling route path
<Link to={`${pathname}/../${RELATED_ID}`}>
Go to Nested Route {RELATED_ID}
</Link>
Demo
Fullcode:
import {
BrowserRouter,
Link,
Route,
Routes,
useParams,
useLocation
} from "react-router-dom";
import "./styles.css";
const RELATED_ID = 1;
const MyComponent = ({ title }) => {
const { pathname } = useLocation();
const { id } = useParams();
return (
<div>
<h1>
{title} {id}
</h1>
<pre>{pathname}</pre>
{id && RELATED_ID && (
<Link to={`${pathname}/../${RELATED_ID}`}>
Go to Nested Route {RELATED_ID}
</Link>
)}
</div>
);
};
export default function App() {
return (
<div className="app">
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/route-a">Route A</Link>
<Link to="/route-b">Route B</Link>
<Link to="/route-a/1">Nested Route A1</Link>
<Link to="/route-a/2">Nested Route A2</Link>
<Link to="/route-b/1">Nested Route B1</Link>
<Link to="/route-b/2">Nested Route B2</Link>
</nav>
<Routes>
<Route
path="/route-b"
element={<MyComponent title="Nested Route" />}
/>
<Route
path="/route-a"
element={<MyComponent title="Nested Route" />}
/>
<Route
path="/route-b/:id"
element={<MyComponent title="Nested Route" />}
/>
<Route
path="/route-a/:id"
element={<MyComponent title="Nested Route" />}
/>
<Route path="/" />
</Routes>
</BrowserRouter>
</div>
);
}
In react-router-dom v5
// Receive the matched url of the current <Route/>
const { url } = useRouteMatch();
In react-router-dom v6
const url = useResolvedPath("").pathname;
In react-router, we cannot push the same route in useHisotry() and cause a re-render. E.g., if component App is showing on route https://localhost:3000 and I click the button Click Me!, it won't cause a re-render:
function App() {
const history = useHistory();
return (
<button onClick={() => {history.push('/')}}> Click Me! </button>
)
}
I want to achieve similar functionality, but I am unsure about the approach or what I am missing.
My current route looks like this: https://localhost:3000/user/1
I want to go to user/2 by clicking a button.
My code looks like the below:
<Route exact path="/user/:userId" component={User} />
function User() {
const history = useHistory();
return (
<button onClick={() => {history.push('/user/2')}}> Click Me! </button>
)
}
The above code changes the route but doesn't re-render the component. How can I fix this issue?
Thanks
I don't recommend using history for this case.
If you really need to, inside User component get userId parameter and react on that.
<Route exact path='/user/:userId' component={User} />
const User = () => {
const { userId } = useParams();
return (
<div>userId: { userId }</div>
);
}
export default User;
My advice is to upgrade to react router dom v6 and use useNavigate , tutorial here
once you import useNavigate from react-router-dom
let navigate = useNavigate();
and on your button you call this function on click passing your desired url
<button onClick={()=> navigate('/users/2')}
Your component's info wont change because you arent rendering anything dynamically in it, so you should grab the userid from the url, and then lets say display it. Check Docs
As the answer below, you can do it exactly as he said.
const { userId } = useParams();
return (
<div>userId: { userId }</div>
);
I am new to React and creating a Tic-Tac-Toe game. I want to create a starting page with three options :
Start
Rules
Exit
On clicking the start button I want to render the component which consists of the original game. On clicking rules I want to render a page showing rules. I have created seperate components for the three buttons and also the game itself.
Screenshot-Start Page
Screenshot-Main Game
My Tic-Tac-Toe Repo
To redirect to a new page containing one of your component you can use react router :
https://v5.reactrouter.com/web/guides/quick-start
and use your button as a Link or use the useHistory hook in your onClick function
If you just want to render a component on the current page when you click on a button you can simply use a condition with a state like so :
...
const [isStart, setIsStart] = useState(false)
...
{isStart ? <Start> : <Button onClick={() => setIsStart(true)}>Start</Button>}
You have to use a React Router which is responsible for showing different components on different url paths, eg.:
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import LandingPage from './landing-page/LandingPage';
import Details from './details/Details';
const Router = () => {
return (
<React.Fragment>
<Switch>
<Route path={`/`} exact render={() => <LandingPage />} />
<Route path={`/details`} exact render={() => <Details />} />
</Switch>
</React.Fragment>
);
};
export default Router;
and then just redirects to those paths on click:
handleClick = (e) => {
e.preventDefault();
history.push('/results');
}
return (
<Button onClick={handleClick}>Results</Button>
);
I'm using the following versions:
`"react-router": "^5.2.0",`
`"react-router-domreact-router": "^5.2.0",`
Not sure if my current setup is React-router 5 friendly or not, I was using a version prior to v5 before this.
The problem in this example is with <Route component={withTracker(InterviewContainer)} path="/interviews/companies/:companyId" /> and <Link/>
Here's my scenario:
Home page loads with a list of company links
Click on a company <Link /> which routes me to /interviews/companies/:companyId
Page loads fine, I see images, etc. for that particular company
Click browser's Back button
Click on a different company <Link /> that points to a different companyId
Problem: for #5, when the company page initially loads, it's loading with stale images and data for some reason. So in other words, I'm seeing the previous company's data & images from step #2 briefly until my React hook makes a new call to get data for this new CompanyId and repaints the browser with the right data (data for the companyId represented in the new route)
index.tsx (note the use of BrowserRouter here)
import { BrowserRouter as Router } from 'react-router-dom';
//...more code and then:
render(
<>
<div className="Site">
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>
</div>
<Footer />
</>,
);
App.ts
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
...more code and then here are my routes:
<Switch>
<Route component={withTracker(HomePageContainer)} exact path="/" />
<Route
path="/companies/:companyId/details"
render={(props: RouteComponentProps<{ companyId: string }>) => (
<CompanyDetailContainer {...props} fetchCompanyNew={fetchCompanyNew} httpRequest={Request} useFetchCompany={useFetchCompany} />
)}
/>
<Route component={withTracker(InterviewContainer)} path="/interviews/companies/:companyId" />
<Route component={withTracker(About)} path="/about" />
<Route component={withTracker(Container)} path="/" />
<Route component={withTracker(NotFound)} path="*" />
</Switch>
Here is how the company Link is coded:
Note: I am using Redux State
"react-redux": "^7.2.1",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
InterviewContainer.tsx (the parent that does the company fetching)
class InterviewContainer extends Component<PropsFromRedux & RouteComponentProps<{ companyId: string }>> {
componentDidMount() {
const { fetchCompany } = this.props;
const { companyId } = this.props.match.params;
fetchCompany(companyId);
}
render() {
const { company } = this.props;
return (company && <Interview className="ft-interview" company={company} />) || null;
}
}
const mapState = (state: RootState) => ({
company: state.company.company,
});
const mapDispatch = {
fetchCompany: fetchCompanyFromJSON,
};
const connector = connect(mapState, mapDispatch);
type PropsFromRedux = ConnectedProps<typeof connector>;
export default withRouter(connect(mapState, mapDispatch)(InterviewContainer));
LinkItem.tsx (one of the children rendered by InterviewContainer and receives the company from InterviewContainer)
render() {
const { company } = this.props,
uri = company.notInterviewed ? `companies/${company.id}/details` : `/interviews/companies/${company.id}`,
className = `margin-top-10 margin-bottom-10 ${company.notInterviewed ? 'ft-company-not-interviewed' : ''}`;
const link = (
<Link className={className} id={company.id.toString()} to={uri}>
<span id="company-name">{company.name}</span>
</Link>
);
}
I think I may have to reset Redux state on route change. I see people in the past have used LOCATION_CHANGE but that's outdated and that's a constant provided by third party redux libs that are no longer supported. So not sure how to do that with Redux v7+
So I think I just need a way to detect a location change and then somehow update my react store to reset company (set company: state.company.company, to undefined from my redux action)
I know things like this can be cumbersome. Have you tried passing in state with the Link as <Link to={uri} state={...someState} />. Then wherever it is loading it should rerender or reset props according to that. Maybe throw some skeleton loaders or conditional rendering logic.