Yes, it is very much possible to switch routes while keeping the DOM rendered, but hidden!
If you are building a SPA, it would be a good idea to use client side routing. This makes your task easy:
For hiding, while keeping components in the DOM, use either of the following css:
.hidden { visibility: hidden }
only hides the unused component/route, but still keeps its layout.
.no-display { display: none }
hides the unused component/route, including its layout.
For routing, using react-router-dom
, you can use the function children prop on a Route
component:
children: func
Sometimes you need to render whether the path matches
the location or not. In these cases, you can use the function children
prop. It works exactly like render except that it gets called whether
there is a match or not.The children render prop receives all the same
route props as the component and render methods, except when a route
fails to match the URL, then match is null. This allows you to
dynamically adjust your UI based on whether or not the route matches.
Here in our case, I'm adding the hiding css classes if the route doesn't match:
App.tsx:
export default function App() {
return (
<div className="App">
<Router>
<HiddenRoutes hiddenClass="hidden" />
<HiddenRoutes hiddenClass="no-display" />
</Router>
</div>
);
}
const HiddenRoutes: FC<{ hiddenClass: string }> = ({ hiddenClass }) => {
return (
<div>
<nav>
<NavLink to="/1">to 1</NavLink>
<NavLink to="/2">to 2</NavLink>
<NavLink to="/3">to 3</NavLink>
</nav>
<ol>
<Route
path="/1"
children={({ match }) => (
<li className={!!match ? "" : hiddenClass}>item 1</li>
)}
/>
<Route
path="/2"
children={({ match }) => (
<li className={!!match ? "" : hiddenClass}>item 2</li>
)}
/>
<Route
path="/3"
children={({ match }) => (
<li className={!!match ? "" : hiddenClass}>item 3</li>
)}
/>
</ol>
</div>
);
};
styles.css:
.hidden {
visibility: hidden;
}
.no-display {
display: none;
}
Working CodeSandbox: https://codesandbox.io/s/hidden-routes-4mp6c?file=/src/App.tsx
Compare the different behaviours of visibility: hidden
vs. display: none
.
Note that in both cases, all of the components are still mounted to the DOM!
You can verify with the inspect tool in the browser's dev-tools.
Reusable solution
For a reusable solution, you can create a reusable HiddenRoute component.
In the following example, I use the hook useRouteMatch
, similar to how the children
Route prop works. Based on the match, I provide the hidden class to the new components children:
import "./styles.css";
import {
BrowserRouter as Router,
NavLink,
useRouteMatch,
RouteProps
} from "react-router-dom";
// Reusable components that keeps it's children in the DOM
const HiddenRoute = (props: RouteProps) => {
const match = useRouteMatch(props);
return <span className={match ? "" : "no-display"}>{props.children}</span>;
};
export default function App() {
return (
<div className="App">
<Router>
<nav>
<NavLink to="/1">to 1</NavLink>
<NavLink to="/2">to 2</NavLink>
<NavLink to="/3">to 3</NavLink>
</nav>
<ol>
<HiddenRoute path="/1">
<li>item 1</li>
</HiddenRoute>
<HiddenRoute path="/2">
<li>item 2</li>
</HiddenRoute>
<HiddenRoute path="/3">
<li>item 3</li>
</HiddenRoute>
</ol>
</Router>
</div>
);
}
Working CodeSandbox for the reusable solution: https://codesandbox.io/s/hidden-routes-2-3v22n?file=/src/App.tsx
HashRouter
orMemoryRouter
an option? – Santanasantayana