Update: the below has been resolved, but I'm running into a similar issue:
In my gallery template (for an example page see here everything loads fine if navigated to via the site. On refresh, the grid parent div (GalleryGrid in styled-components) is erased/unstyled/replaced with a blank div. This actually happens after refresh, during Gatsby's hydration.
I've tried replacing the styled component with a regular div styled manually, with no luck. Unsure what's going on!
I'm running into a really strange error!
I've built a site using Gatsby, sourcing from Prismic.io, and using styled-components to style. I use framer-motion for page transitions and have added my layout component to gatsby-browser so that only page content gets transitioned, and have added similar code to gatsby-ssr to fix some initial ssr errors.
What's happening now is that whenever a page other than an index is visited directly (try this one) some components are not rendered properly. On this example, the date towards the top of the page is replaced with a <Body> component (which is styled differently to the intended <Date> component), and the actual content has been truncated and only shows the first <p>.
If you navigate to the 'portfolio' section and then navigate back to the Catalogue page, the page shows correctly - the date is now a <Date> component and the whole body text is displayed.
A similar error happens with 'essay' nodes - for example, on this page the image is intended to be in a component called ImageContainer but is replaced by an extra EssayContainer when refreshed or accessed directly (instead of navigating to the page via site navigation).
I honestly have no clue what's going on here or what could be causing this error - whether I've done something wrong or this is a bug in Gatsby's SSR or styled-components/the gatsby SC plugin. It works as expected when running gatsby develop, so must be something in the build or the SSR.
My gatsby-browser:
const transitionDelay = 500
export const wrapPageElement = ({ element, props }) => {
return <Layout {...props}>{element}</Layout>
}
export const shouldUpdateScroll = ({
routerProps: { location },
getSavedScrollPosition,
}) => {
if (location.action === "PUSH") {
window.setTimeout(() => window.scrollTo(0, 0), transitionDelay)
} else {
const savedPosition = getSavedScrollPosition(location)
window.setTimeout(
() => window.scrollTo(...(savedPosition || [0, 0])),
transitionDelay
)
}
return false
}
And my gatsby-ssr:
const transitionDelay = 500
export const wrapPageElement = ({ element, props }) => {
return <Layout {...props}>{element}</Layout>
}
And my repo is here.
Any help is greatly appreciated!
This turned out to be two issues!
The problem with some components being swapped on build was due to a media query library I was using - react-responsive- not having SSR support. I migrated to #artsy/fresnel and that resolved a bunch of the issues.
The problem with the content being truncated seems to be an issue with styled-components not playing so nicely with SSR. I had styled components setting inner HTML directly - nesting another div inside the component and setting the html from there did the trick:
<Description>
<div
dangerouslySetInnerHTML={{
__html: data.prismicGallery.data.description.html,
}}
/>
</Description>
This resolves almost everything - I'm still having a problem with Gatsby's hydration replacing/unstyling some elements, but I suspect that's a different issue (added to original question).
Related
I've read a bit about <ThemeProvider />, React.lazy, React Helmet, and a few others but they don't seem to do what I would like; unless they do and I'm looking at their uses incorrectly or am using the wrong terms to search and research - if so, please let me know as it would be very helpful to have someone go "These are what you want to use, you just need to continue working with them to get it".
I have a website with shared components that have two stylesheets each containing color and sizing variables, one for Light and one for Dark. Ideally, I want the shared components to determine when to load the respective Light and Dark styles without needing to place conditional within each and every component, ie:
import lightStyle from 'div.module.scss';
import darkStyle from 'div.dark.module.scss';
...
<div className={ApplicationVariableService.isDarkMode() ? darkStyle.div : lightStyle.div}>
<p className={ApplicationVariableService.isDarkMode() ? darkStyle.p: lightStyle.p}>
{children}
</p>
</div>
...
rather I want to apply className={styles} to all the elements.
I got close with the following:
import React from 'react';
let styles;
if (ApplicationsVariableService.isDarkMode()){
import("./div.dark.module.scss")
.then((res) => {
styles = res;
});
} else {
import("./div.module.scss")
.then((res) => {
styles = res;
});
}
...
return(
<div className={styles}>{children}</div>
)
The downside is that this block would need to be added to each and every component not only replicating the same thing over and over but should additional stylesheets come in later all of these blocks will need to be modified.
I'm thinking I could import then export all of the component stylesheets into a light_index.js and dark_index.js then add the if/else to the main index.js file that exports all of the components for use in the App. That could potentially hit load speed because I don't want to load all of the component stylesheets, just the stylesheets for the component rendering on the page.
My current file structure:
// shared components
src
stylesheets
_light_variables.scss
_dark_variables.scss
components
div
div.jsx
div.module.scss // imports light_variables
div.dark.module.scss // imports dark_variables
index.js
// App
app
src
components
page.jsx
index.js
Maybe I'm over thinking it? Can I have this file structure and the desired behavior without loading both light and dark stylesheets for every component? I am thinking about the future where more stylesheet might be added later and I wouldn't want to have to edit every components conditional import statement or the condition rendering in each and every element. Was I looking in the right direction with <ThemeProvider /> and React.lazy?
Guidance would be much appreciated!
Update: I completely redid my approach as explained in my other question. For edification and anyone else wanting to do something similar, if you have a solution please share it!
I have an odd problem where a dynamic link inside a component works in most places on the site (static pages), but not inside a particular React Component - Algolia InstantSearch (functionally very similar to this component: https://github.com/algolia/react-instantsearch/blob/master/examples/next/components/app.js).
I've tried using the 'usual' Link component, i.e:
<Link href={'/products/[permalink]'}
as = {`/products/${permalink}`}
passHref>
....
</Link>
Plus trying to make use of the useRouter hook as outlined here: https://mariestarck.com/a-beginners-guide-to-dynamic-routing-in-next-js/
const router = useRouter();
const ROUTE_PRODUCT_PERMALINK = "/products/[permalink]";
const navigate = (permalink) =>
router.push({
pathname: ROUTE_PRODUCT_PERMALINK,
query: { permalink }
});
return (
<a className="group relative" onClick={() => navigate(permalink)}>
Upon clicking, the URL changes correctly and getInitialProps appears to run, but the current page does not unmount. This only occurs when the component is a child of the InstantSearch React Class.
Thanks!!
This actually turned out to be an issue with my theme - certain hooks relied upon an animation to unmount (as part of the framer-motion library), which was not passed to the InstantSearch App component. Wrapping my Search Results page in <motion.div> appeared to fix the issue - the issue was not with the routing itself.
I know my question could simply have a "This cannot be done, this defines the purpose of SPA". But...
I navigate to mydomain.com in my REACT web app. This page loads data from the backend and populates elaborate grids. It takes it about 2 seconds to load and render.
Now I click a link on that elaborate page and navigate to mydomain.com/otherPage. When I click the browser's BACK button to return to mydomain.com, it's blank, and has to be rebuilt from scratch as SPA dictates the DOM must be erased and re-built with every page change (at least the page-specific dynamic parts of it, as routes can be inside a fixed layout of header/footer etc). I get that...
Other than migrating to nextJS and using SSR....
Is there any magic solution in REACT to somehow 'retain' the DOM for a page when navigating out of it, so that when you browser-back into it, that page is instantly shown and not rendered from scratch?
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
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 than display: none; and the elements do not take any space contrary to visibility: 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...
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 the React 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.
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 ();
}
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
Describe the Problem
I am making a very simple ReactJS/Gatsby website for someone and I am having an issue with one of my functional styled components when it re-renders. The problem is that it is causing the window to jump (scroll) after the re-render is complete.
The re-render is triggered by the user clicking on a span element (enclosed in an li element) which fires a function.
The list of li elements is determined by the state of the component. The overall parent component has a fixed height which is why I am having trouble diagnosing the issue.
What I Expect to Happen
The component to re-render and the window's scroll position to remain where it was when the user initiated it.
What Actually Happens
When the user clicks the element the page appears to jump (scroll). Sometimes it does so and remains in the new position, sometimes it does so and then returns to the original scroll position.
What I've Tried
I've tried following advice from other questions which suggest using event.preventDefault() and others which suggest moving the styling out of the component itself and, instead, opting for using classes.
Neither of these solutions worked.
I have managed to definitively find that the issue is due to setActiveTabs -- which causes the re-render of the ul element -- as logging window.scrollY both prior to it firing and after it completes displays a different value.
Edit 2:
I have managed to figure out that the issue is with making the list items targetable. It seems that either adding the tabIndex="0" attribute or making the li child an interactive element causes this bug.
Does anyone know a way around this?
Edit
The full frontend source code can be found in the following GitHub repo: https://github.com/MakingStuffs/resinfusion
In order to solve the issue I needed to prevent the clicked element from being targeted on the re-render. In order to do this I edited the clickHandler so that it uses element.blur() after setting the state.
The click handler is as follows:
const forwardClickHandler = event => {
setLoading(true)
const clickedSlug =
event.target.closest("button") !== null
? event.target.closest("button").getAttribute("data-slug")
: event.target.children[0].getAttribute("data-slug")
const categoryObject = getNeedle(clickedSlug, categories, "slug")
const subCatObject = getNeedle(clickedSlug, subCategories, "slug")
const serviceObject = getNeedle(clickedSlug, services, "slug")
const associatedChildren = getAssociatedChildren(
categoryObject
? categoryObject
: subCatObject
? subCatObject
: serviceObject
)
setBgImage(associatedChildren[0].thumb.localFile.childImageSharp.fluid)
setActiveTabs(associatedChildren)
event.target.blur()
return setTimeout(() => setLoading(false), 1000)
}
I was very surprised that a simple Link component is not working in Next.js when you want to use an external URL and HTML Button tag inside it.
Below you can see how I tried to solve the problem:
Approach number 1:
<Link href="https://stackoverflow.com/">
<button>StackOverflow</button>
</Link>
Approach number 2 (link without protocol):
<Link href="//stackoverflow.com/">
<button>StackOverflow</button>
</Link>
Approach number 3 (link without protocol and with Link attribute prefetch set to false or even true):
<Link href="//stackoverflow.com/" prefetch={false}>
<button>StackOverflow</button>
</Link>
IMPORTANT NOTE
Of course, mentioned case it's working when the URL is internal, like that:
<Link href="/stackoverflow">
<button>StackOverflow</button>
</Link>
or when I will change HTML button tag into HTML A tag, like that:
<Link href="//stackoverflow.com/">
<a>StackOverflow</a>
</Link>
In my case, I want to use the HTML button tag or any other UI component inside the Next.js Link component.
1. Solution for UI components inside Next.js Link component.
I have study Next.js documentation in more details and I found a very useful attribute to make an external link for any internal UI components (Semantic UI, Material UI, Reactstrap, etc.) inside Link component.
Let's take as an example a simple Semantic UI button component.
To add an external link to the Next.js Link component we should use attribute passHref. This attribute is set to false by default. This attribute forces Link to send the href property to its child.
import { Button } from 'semantic-ui-react';
import Link from 'next/link';
const Example = () => (
<Link href="https://stackoverflow.com/" passHref={true}>
<Button>StackOverflow</Button>
</Link>
)
export default Example;
2. Solution for HTML elements (different that tag A)
Inside Next.js documentation you can find below sentences:
External URLs, and any links that don't require a route navigation
using /pages, don't need to be handled with Link; use the anchor tag
for such cases instead.
And I have to write that it is obvious, so in that case, if you need to use any other tag, for example, HTML button, you should use onClick event on it without Link component.
The above code will look like this:
const clickHandle = () => {
document.location.href = 'https://stackoverflow.com/';
}
const Example = () => (
<button onClick={clickHandle}>StackOverflow</button>
)
export default Example;
UPDATE:
Of course, I agree with devs who are writing that for external links we should not use the Link component. The best solution here is to use just pure HTML a tags or JS redirect solution on click event as it has been shown in point 2 (or any similar way). Worth to mention, that you can build your own component and based on the passed href attribute you can switch between Link component and HTML a tag, like that:
// custom simple smart Link component
import Link from 'next/link';
const SmartLink = (link, url) => {
const regEx = /^http/;
return regEx.test(url) ? <Link href={url}>{link}</Link> : <a href={url}>{link}</a>;
}
export default SmartLink;
// ways to call the component
import SmartLink from 'path/to/SmartLink'; // set correct path
// somewhere inside the render method
// the below will use HTML A tag
<SmartLink href="https://stackoverflow.com" link="external StackOverflow website" />
// the below will use Next.js Link component
<SmartLink href="/stackoverflow" link="internal StackOverflow page" />
The Link component is only for linking between pages within your Next app. Passing an external URL is not supported behaviour, and should give you an error that links to this page, which includes this section:
Why This Error Occurred
Next.js provides a router which can be utilized via a component
imported via next/link, a wrapper withRouter(Component), and now a
hook useRouter(). When using any of these, it is expected they are
only used for internal navigation, i.e. navigating between pages in
the same Next.js application.
Either you passed a non-internal href to a next/link component or you
called Router#push or Router#replace with one.
Invalid hrefs include external sites (https://google.com) and mailto:
links. In the past, usage of these invalid hrefs could have gone
unnoticed but since they can cause unexpected behavior. We now show a
warning in development for them.
If you render an <a> inside, the href gets passed on to that and works as expected using native browser behaviour, but other elements can't use that so you would have to handle that case yourself.
I'd suggest looking at what you're trying to achieve though -- what's wrong with using an <a> tag? It seems like the right tool for the job.
In my opinion, the accepted answer is wrong. passHref is used when the <a> tag is not a child of <Link>. For external URLs, simply use <a> without <Link>, see below.
const link = props.link.charAt(0) === '/' ? <Link as={stripUrlPlaceholder(props.link)} href="/">
<a>{image}</a>
</Link> : {image};
return <div className="banner">
{link}
</div>;
Simply stated, use the <a> tag instead of the next <Link>. The next <Link> is for internal navigation.
Example 1.
<a
href='https://www.facebook.com/queueunderstop/'
target={"_blank"}
rel={"noreferrer"}>
<Image
className='gb'
src='/images/icons/fb.png'
alt='facebook'
width={25}
height={25}
/>
</a>
This resolves the issue of the link opening a new link while simultaneously closing the main site. The main things to note are the attributes:
target={"_blank"}
rel={"noreferrer"}
I tried various combinations as well and landed on this after reading the documents carefully even though the documents do not say it implicitly. The documents cover more of does and not all the hypotheticals.
I had the same issue, somehow I tried the above answers, it wasn't really helpful. What I found is that if you add https or HTTP:// in, it will surely automatically allow you to open external web. Here for a sample:
<a href={`https://${Your link}`}> Open external Link </a>
There is no need to use the next/link for external links, as it's only for client-side transitions between routes. Link is not intended for linking outside your app.