score:7
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
score:1
using out of the box routing, i would say: it's impossible.
but who said we need to use routes?
solution 1:
why not using portals?
this probably won't work if you want to 'retain' the dom for any navigation on your page. but if you want to 'retain' it on only one specific page, then you could just open a fullscreen portal/modal/dialog (or whatever you wanna call it).
solution 2:
if you want to 'retain' the dom for all navigation, then you could also write a "router-component" yourself.
the logic of your component could look like this:
first you need a lookup-table. give every url a related component that should be rendered, when the url is called.
- check if the target url has previously been open
- if no: create a new div and open the matching component (from the lookup) in it. bring that div to the front (z-index)
- if yes: bring the related (already existing) div to the front (z-index)
writing such a component shouldn't be too hard. i only see two problems with it:
- performance: if you got many overlapping components open at the same time, this could slow down your page (depending on how many pages and content you got)
- on refresh everything gets lost
score:2
i've misread the question initially. i'll leave the initial answer for the case when a user goes to a page on another domain.
updated answer
you've wrote in the comments
i was clear enough
well... judging by discussions here, that's not the case.
here some points to consider, and when you'll answer them that should be the solution to your problem... whatever it is:
- do you really need to make network calls on the components' mount? for an spa it's usually a good idea to decouple your state and visual representations of it (plural!).
- obviously, you need come caching mechanism. but should it be "cache" of rendered nodes of some sort (as have been suggested in every other answer) or cache of data, received from the net, or both, is up to you. and ssr - is not a caching mechanism. it exists for other reasons.
- do you use any
router
? if, yes, then which one and how? because some of then can retain the previous route in memory, so with a little bit of luck you could've never stumble on you blank page problem. and that can be the answer. - but maybe
mydomain.com/otherpage
is not under control of thereact
or/and maybe it's not a true spa we a talking about here. and the effects of going to this page is the same as going to another domain? then my initial answer holds.
in a nutshell:
-
is there any magic solution in react to somehow 'retain' the dom for a page when navigating out of it.
yes, if by navigating out of it and a page you mean navigating to another route in you spa and just rendering some other component, without executing a get request through a "standard"
<a>
-click,window.location.href
change or something similar which will lead to the browser initiating a new page loading.for that just read your router's docs.
no if your are actually leaving your spa.
for this case i would suggest
serviceworker
. as to my taste, it's a much simpler and more flexible solution compared to a change of the architecture of your project with ssr.
-
as spa dictates the dom must be erased and re-built with every page change
not at all. dom will be erased only if the state of a component or the
props
are changed. but to help you with that we need to see the code.
initial answer
it's not totally clear what is your question about. you are focused on the idea of preventing the dom rebuild, but at the same time you're saying that the bottleneck is the api calls. and they're two quite different things to deal with.
and possible solution to you problem heavily depends on the architecture of you code.
if you have control over the server side, you can setup caching for your calls. if not, you can setup caching on the client side in a pwa
-style manner.
if you have a centralized store, you can save its state to the localstorage
on an <a>
click and repopulate your page from the localstorage
when the user gets back onto your page. if not you can resort to service worker api again to intercept api calls and to return cached responses. (or just override fetch
or whatever)
you can even "emulate" the ssr by saving html to the localstorage
and showing it right away when the user gets back. (but the page will not be fully functional for a couple of seconds and need to be replaced at the moment you api-calls are completed)
but there is no feasible way to prevent dom rebuild, because while theoretically possible, it's probably impractical to cache the whole react internal state. and if your main problem is indeed the dom rebuild itself then probably your code is in need of serious optimizations.
score:2
one solution i often use, is to persist that data in location.state. and then when navigating back, the component first checks for data in location.state before attempting to fetch the data again.
this allows the page to render instantly.
const example = (props) => {
const history = usehistory();
const location = uselocation();
const initialstate = location.state;
const [state, setstate] = usestate(initialstate);
useeffect(() => {
const persistentstate = state;
history.replace({ pathname: location.pathname }, persistentstate);
},[state]);
return ();
}
score:3
for api calls
you can simply put your generated elements that need intensive calculation in a state, in a component that never gets unmounted while changing page.
here is an example with a parent
component holding 2 children and some jsx displayed after 5 seconds. when you click on the links you navigate to children, and when you click on browser's back button, you get back on the url path. and when on / path again, the "intensive" calculation needing element is displayed immediately.
import react, { useeffect, usestate } from "react";
import { route, link, browserrouter as router } from "react-router-dom";
function parent() {
const [intensiveelement, setintensiveelement] = usestate("");
useeffect(() => {
const intensivecalculation = async () => {
await new promise((resolve) => settimeout(resolve, 5000));
return <p>intensive paragraph</p>;
};
intensivecalculation().then((element) => setintensiveelement(element));
}, []);
return (
<router>
<link to="/child1">go to child 1</link>
<link to="/child2">go to child 2</link>
<route path="/" exact>
{intensiveelement}
</route>
<route path="/child1" exact>
<child1 />
</route>
<route path="/child2" exact>
<child2 />
</route>
</router>
);
}
function child1() {
return <p>child 1</p>;
}
function child2() {
return <p>child 2</p>;
}
about redisplaying quickly the dom
my solution above works for not doing slow things twice like api calls. but following the remarks of mordechai, i have made an example repository to compare dom loading time of really big html for 4 solutions when using browser back button:
- plain html without javascript (for reference)
- react with the code example i gave above
- next.js with next's page routing
- a css solution with react and
overflow: hidden; height: 0px;
(more efficient thandisplay: none;
and the elements do not take any space contrary tovisibility: hidden;
,opacity: 0;
etc. but maybe there is a better css way)
each exemple loads an initial page of 100 000 <span>
elements, and has links to navigate to small pages, so that we can try the back button on the browser.
you can test yourself the static version of the examples on github pages here (the pages take several seconds to load on a normal computer, so maybe avoid clicking on them if on mobile or so).
i've added some css to make the elements small enough to see all of them on the screen, and compare how does the browser update the whole display.
and here are my results:
on firefox:
- plain html loads in ~2 sec, and back button displays page in ~1 sec
- next app loads in ~2 sec, and back button displays page in ~1 sec
- css solution in react app loads in ~2 sec, and back button displays page in ~1 sec
- react app loads in ~2.5 sec, and back button displays page in ~2 sec
on chrome:
- css solution in react app loads in ~2 sec, and back button displays page in ~1 sec
- react app loads in ~2.5 sec, and back button displays page in ~2 sec
- plain html loads in ~8 sec, and back button displays page in ~8 sec
- next app loads in ~8 sec, and back button displays page in ~8 sec
something important to note also: for chrome when next.js or plain html take 8 seconds, they actually load elements little by little on the page, and i have no cache with the back button.
on firefox i don't have that little by little displaying, either there is nothing or everything is displayed (like what i have on chrome with react state usage).
i don't really know what i can conclude with that, except maybe that testing things is useful, there are sometimes surprises...
Source: stackoverflow.com
Related Query
- React, Single Page Apps, and the Browser's back button
- Hitting Back button in React app doesn't reload the page
- React JS page keeps refreshing when using the back button
- React Router - Go back to the last page visited, and not the default component
- React Navigation - navigate and set history for the back button
- Redirect to welcome page on pressing back button in react Single page application
- How to I prevent user from accessing the page of my react app directly by entering the URL, and redirect them back to login page
- When I click cancel button in react why the page is reloading and why its path also changing in react?
- How to get a login button to navigate to a new page once clicked. I have downloaded and used the React router dom
- My react SPA navigation currently renders the page for about a second and then goes back to my Main page
- Multiple react apps in a single page and keep them in sync
- Why does my react page go back without the button being clicked?
- Passed the button text to state, but text reverted back during page refresh in React hooks
- React Router: How to re-render the page when the user presses the back button
- I am trying to go back to the previous page on click of a button in react
- Next.js + React Go back to the previous page
- React generated button on my form keeps refreshing the page
- structure of a Backbone and React single page app
- Scroll page to the nested React component on a button click
- How to implement the OAuth2 Authorization code grant with PKCE for a React single page application?
- Prevent browser back button for a specific page only in react
- How do I set system preference dark mode in a react app but also allow users to toggle back and forth the current theme
- How to display the state on the same page when clicked Submit button in react
- In single page apps, is it standard to do sorting and filtering on the server?
- React Router `history` and `location` mismatched after back button
- how to update a route in React Router without re-mounting the component in a single page app?
- Memory Router doesn't use the browsers back button
- how to change the font color, font size, and button color in React
- How to intercept back button in a React SPA using function components and React Router v5
- Scroll down the page to another React component on a button click
More Query from same tag
- How to update local state after the execution of multiple Apollo queries and mutations?
- Processing an Object During Fetch Returns 'undefined'
- request data, pass it to another component and return it in react
- toggling a class depending on url in React
- Ag grid getLastDisplayedRow() does not work properly
- Where should i check if a user is authenticated server or client?
- How to define the type of Key-Callback pairs in Typescript
- ReactJS - componentWillReceiveProps uses
- React - How can I parse large JSON objects for graphing. Struggling to do so with State
- React Redux - very slow onChange
- Updating State Object Does Not Rerender Dynamically Created Component
- Can't resize d3 v6 in react hooks?
- Getting value of input causing flickering in React
- How is it possible to show the menu of a chart with React Highcharts?
- Best way to render multiple child components
- Child routes not rendering
- Render Previous State in React with Redux
- Comparing 2 different set of data array
- How to synchronize multiple React lucky draw web-app at the same time?
- How can I create interactive checkboxes in my React JS app?
- Where to put User ID in a GA tracking flow?
- React.js can't change checkbox state
- Is there a way to ignore a TypeError (axios get request)
- Appending React States to another State
- where call setInterval in react
- changes material-ui checkbox state checked, unchecked or indeterminated based on other checkbox click, without using state in reactjs
- Error using GSAP's scrollToPlugin with create-react-app
- Call method from sibling component using ReactJS
- Cloudinary return empty url on first try
- ReactJS Load Images on Click