Nextjs check if initial render was done on server side - reactjs

I have an image lazy load component that just renders the image src when on the server, and renders a loader when on client pending when the actual image is lazyloaded.
The problem.
After the initial render and client side takes over, the image src mismatch. This is because the server set the actual src, but the client is setting the loader (placeholder).
Question.
Is there a way to detect that this initial render was server rendered? just before the client renders/mounts

You can find out if it is currently executed on the server by checking for req attribute inside getInitialProps
Example page code
function Page({ isServer }) {
return <div>Is this page on the server? - {isServer ? 'YES' : 'NO'}</div>;
}
Page.getInitialProps = async ({ req }) => {
return { isServer: !!req };
};
export default Page;
Some info on official repo about isServercheck

You can check that typeof Window === 'undefined'

The fix is to use useEffect as described in the Next.js docs at https://nextjs.org/docs/messages/react-hydration-error
do this:
import { useState, useEffect } from 'react'
function AnyComponent() {
const [isSSR, setIsSsr] = useState(true)
console.log(`isSSR: `, isSSR);
useEffect(() => setIsSsr(false))
return (
<span>Is SSR? {isSSR ? 'Yes!' : 'No!'}</span>
)
}
Obviously, this trivial example will update so quickly that you would only ever see "Is SSR? No!" but the first render will be yes. Check the console.
Why? useEffect is only executed in the browser.

Related

How I can get localstorage data inside getServerSideProps

I am using NextJS 12. I am trying to get local storage object. When I use localstorage inside getServerSideProps I get an error like this ReferenceError: localStorage is not defined. I tried to use it outside the function as well but I still get this error. Is there any way to use it inside getServerSideProps.
export async function getServerSideProps({ query }) {
const id = query.id;
const getData = JSON.parse(localStorage.getItem("form"));
console.log(getData)
return {
props: {},
}
Welcome to StackOverflow, as it refers in the documentation
If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps.
Localstorage is only available on the client side and you are trying to access it in a server side only function , you can use something like
if (typeof window !== 'undefined') {
// your code
const id = query.id;
const getData = JSON.parse(localStorage.getItem("form"));
console.log(getData)
}
Please review this article to get more information on running client side only code.
Another approach would be to use a dynamic import where the hello3 component would contain the code accessing local storage.
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
Another way would be using cookies instead of using localstorage, I had the same problem when I developed my last application and I solved it using the nookies package
Nookies: A collection of cookie helpers for Next.js

Get User Agent in getStaticProps in NextJs

I am using react-device-detect package to detect whether the request is from a mobile or desktop device, it uses user agent of course to do that.
In my NextJS project I want to use it on SSG pages.
Imported the package
import { isMobile, getUA, getSelectorsByUserAgent } from 'react-device-detect';
And using the getStaticProps like this
export async function getStaticProps(context) {
// Device React
const deviceIsMobile = isMobile;
const deviceType = deviceIsMobile ? 'Yes, I am a mobile' : 'Nope, Desktop!';
return {
props: {
mobileDevice: deviceType
}, // will be passed to the page component as props
}
}
And finally in my page's function
function AwesomePage( { mobileDevice } ) {
return(
<h1>{ mobileDevice }</h1>
)
}
No matter what I always see 'Nope, Desktop!'.
In some examples of react-device-detect I noticed that we may use the userAgent from the req param, but does getStaticProps even provide that, I checked and it seems it don't? What's the way around this? I simply want to get user agent on an SSG page through getStaticProps.
Thanks!

load splash screen before nextjs

I have a NextJS website and I want to add a Splash Screen for before website is loaded
but because the Splash Screen is also in the NextJS code, it will loading when nextjs rendered on the server and the JS downloaded and executed on the client. in fact, it's useless because it will execute after the page is ready!
how can I do the Splash Screen before react completely loaded and executed ?
I also use nginx for proxy_pass
use this code
useEffect(() => {
const handleStart = () => { setPageLoading(true); };
const handleComplete = () => {
setPageLoading(false);
};
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);
}, [router]);
and use pageLoding for show splash
For loading screen:
import React from 'react'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((res) => res.json());
// your main function
export default function Profile() {
//for relative and absolute paths
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
//for the loading you can create your custom component and insert instead of div, like this you keep same styling
if (!data) return <div>loading...</div>
if (data) return <div>hello {data.name}!</div>
}
Don't use useEffect hook, use this lib, better code and functionnality.
You have multiple possibility
You can start your Next Application on a page that contain a simple shimmer(splash screen), then replace the URL by url that contain SSR, and finally remove loading page for indexing with robot.txt. You can read more about this tips there.
You can insert inside on ... tag a CSS to show a loader(your splash screen). Then when the SSR function will be in loading the loader will be hide but when the browser download index.html(first file loaded, that contain style tag) the loader will be show before the browser download another assets(images, js, etc) and load it. You can read more about this tips there
The first tips will show loader fast than the second tip but require more steep(create another page, replace url, remove loader page for indexing)
You can do it by using DOMContentLoaded event, here an example:
In your _app.tsx and outside your function:
if (typeof window !== "undefined") {
document.addEventListener("DOMContentLoaded", () => {
// your code here ..
}
}

Use SurveyJS along with Nextjs

As knows NextJS is using the server side rendering, I want to use SurveyJs with it but surveyJS is using some function that must be executed in client side. In my case I wanna use StylesManager.applyTheme but it throws an server error of ReferenceError: document is not defined
any possible way can I use to execute the applyTheme function in client side?
You can achieve this creating a useEffect Hook that consumes an external function to apply this theme:
import {useEffect} from 'react'
useEffect(() => {
const newSurveyCreator = new SurveyJSCreator.SurveyCreator('surveyCreatorContainer', surveyCreatorConfig);
newSurveyCreator.saveSurveyFunc = function() {
console.log(this);
console.log(this && this.text);
};
updateSurveyCreator(newSurveyCreator);
}, []);
For better implementation, I solved the issue using dynamic imports from NextJs
Instead, I imported my SurveyComponent like this:
const SurveyComponent = dynamic(
() => import("../../../../components/survey/PartOne"),
{
ssr: false,
}
);
To make sure my SurveyComponent does not render on server-side.

React.js Router url is resolving from browser but not from Link within App

I have a component setup in react router that has the following url
http://localhost:3000/editing/5f0d7484706b6715447829a2
The component is called from a parent component using the <Link.. syntax and when I click the button in the parent component the url above appears in the browser but my app crashes.
The error applies to the first manipulation the component does with the data - which indicates to me that the app is running before the data is available. This does not happen elsewhere in the app.
×
TypeError: Cannot read property 'reduce' of undefined
If I then refresh the url in the browser it works as intended. The component also loads fine if I just paste the above url into the browser.
The code is below
const Button = (props) => {
//main props
const { buttonFunction, buttonIconName, buttonStyle, visible } = props;
return (
<Link
to='./editing/'+props.documentId}
</Link>
);
};
export default Button;
The called component is below
mport React, { useContext, useEffect } from 'react';
import DocumentEditor from '../documentEditor/documentEditor';
import DocumentContext from '../../context/Document/DocumentContext';
import Spinner from '../layout/Spinner';
//For testing this Id works
// 5f0d7484706b6715447829a2
//Wrappers
const Editing = ({ match }) => {
//styling
const documentContext = useContext(DocumentContext);
const { Documents, myFiltered, getDocuments, loading, getDocument } = documentContext;
useEffect(() => {
getDocument(match.params.documentid);
// eslint-disable-next-line
}, []);
return (
<div>
{Documents !== null && !loading ? (
<DocumentEditor
Document={Documents}
DocumentContext={documentContext}
documentMode='edit'
/>
) : (
<Spinner />
)}
</div>
);
};
export default Editing;
My Thoughts
I suspect it may be due to the form loading prior to the data has been fetched but I don't see how as the code
{Documents !== null && !loading ? (
along with my functions that have been pulling data have been working fine for ages and use async / await should prevent this. Maybe I shouldn't be using a link in this way?
As #Yousaf says in the comments, your specific error is regarding a reduce() call, and we can't see that in the code you posted.
I do see what appears to be a typo, and that may be your issue.
At the top of your file, you're importing DocumentContext with a capital "D"
import DocumentContext from '../../context/Document/DocumentContext';
And inside your Editing component, you're destructuring documentContext with a lowercase "d":
const { Documents, myFiltered, getDocuments, loading, getDocument } = documentContext;
This would lead to { Documents, myFiltered, getDocuments, loading, getDocument } all being undefined. You're then passing Documents and documentContext to the DocumentEditor, which presumably is trying to call reduce() on one of those things that are passed, which are undefined.

Resources