I’m adjusting some functionality for a React app (using React 16.13.1) with React Router v5 and HashRouter. My app displays a list of coupons in a table (using AgGrid) within a component (named Coupons). When a user clicks on a coupon row, I want to open a modal with the coupon details without unmounting the underlying list so that its scroll position and state are preserved.
To achieve this, I’m using the “modal route” pattern. In my Coupons component, my rowClicked handler looks like this:
rowClicked = e => {
console.log("Current path is", this.props.location);
this.props.history.push({
pathname: `/${this.state.objects}/${e.data.id}`, // e.g., "/coupons/3747"
state: { background: this.props.location },
});
};
Here, this.state.objects is “coupons”, and this.props.location (from withRouter) shows:
{ pathname: '/coupons', search: '', hash: '', state: undefined }
In my DefaultLayout
component, I use a <Switch>
with a custom location prop:
const location = useLocation();
const background = location.state && location.state.background;
<Suspense fallback={loading()}>
<Switch location={ background || location }>
{routes.map((route, idx) => (
<Route
key={idx}
path={route.path}
exact={route.exact}
render={(props) => {
console.log("[Switch] Matched route:", route.path);
console.log("[Switch] location used:", background || location);
return <route.component {...props} />;
}}
/>
))}
<Redirect from="/" to="/users" />
</Switch>
</Suspense>
{background && (
<Route path="/coupons/:id">
<ModalRouteForCoupons />
</Route>
)}
My logs indicate that when I click on a coupon, the location becomes:
location: { pathname: '/coupons/3747', ... }
background: { pathname: '/coupons', ... }
So the <Switch>
uses the background (i.e. /coupons), and it correctly matches the route for the list. Additionally, a separate <Route>
renders my modal (ModalRouteForCoupons).
export default function ModalRouteForCoupons() {
const { id } = useParams();
const history = useHistory();
const closeModal = () => {
history.goBack();
};
console.log(id);
return (
<Modal isOpen={true} toggle={closeModal} size="lg">
<ModalHeader toggle={closeModal}>Редактирование купона</ModalHeader>
<ModalBody>
<Coupon
isEmbedded={true}
match={{ params: { id } }}
done={closeModal}
/>
</ModalBody>
</Modal>
);
}
However, the problem is that when I click a row, my list component (Coupons) unmounts and then remounts (I see “Coupons unmount!” and then “mount” logs from the component). This causes the scroll position to reset and data to reload—something I want to avoid.
Additional details:
- My routes.js includes both a route for /coupons (rendering the list)
and /coupons/:id (rendering the coupon detail). - I’m using HashRouter, and my logs show that location.key is
undefined. - I’ve confirmed that in the rowClicked method,
this.props.location.pathname is /coupons, so background is set
correctly.
My questions:
-
Why does React Router remount the Coupons component (i.e. my list)
when I navigate to /coupons/3747 with a background state, even
though my is set to use background || location? -
Is this behavior due to using HashRouter (which might not provide a
unique location key), or is it something in my route configuration?
Any insights or suggestions on how to prevent the list from remounting (thus preserving its scroll position and internal state) while still having a modal that shows the coupon detail (with the URL reflecting /coupons/:id) would be greatly appreciated!