ReferenceError: document is not defined inside Next.js client component [duplicate] - next.js

This question already has answers here:
Next.js: document is not defined
(9 answers)
Closed 10 days ago.
I have a client component ("use client") created using Next.js
"use client"
import type { ReactPortal } from "react"
import { createPortal } from "react-dom"
interface PortalProps {
children: React.ReactNode
}
export function Portal(props: PortalProps): ReactPortal | null {
return createPortal(props.children, document.body)
}
Whenever I visit the page which uses <Portal /> component it throws an error in the console
event - compiled client and server successfully in 469 ms (1685 modules)
ReferenceError: document is not defined
at Portal (webpack-internal:///(sc_client)/./src/components/portal/portal.component.tsx:9:98)
How can I fix that?
P.S. This is my package.json
// package.json
{
// cut
"next": "13.1.6"
// cut
}

I just found that Client components prerender on the server which doesn't have a access to the document object. That's why it says document is not defined.
There are many options how it can be fixed
Wrap document in typeof window !== undefiend check to make sure the document is only accessible in the browser enrironment (window is undefined during prerender).
Use useEffect + useState + condition
My solution to the problem looks like this
"use client"
import { type ReactPortal, useEffect, useState } from "react"
import { createPortal } from "react-dom"
interface PortalProps {
children: React.ReactNode
}
export function Portal(props: PortalProps): ReactPortal | null {
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(true)
}, [])
return isMounted ? createPortal(props.children, document.body) : null // createPortal will not be rendered on the server. Only on the client after hydration
}
See also https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components
Client Components
Client Components enable you to add client-side interactivity to your
application. In Next.js, they are prerendered on the server and
hydrated on the client. You can think of Client Components as how
Next.js 12 and previous versions worked (i.e. the pages/ directory)
See

Related

Error: Minified React error #321 on Wordpress custom block plugin

Whenever I write a useEffect() inside a component function of my block plugin, the edit page goes blank and the console logs the message:
react_devtools_backend.js:4026 Error: Minified React error #321; visit https://reactjs.org/docs/error-decoder.html?invariant=321 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at Object.it (react-dom.min.js?ver=17.0.1:9:43163)
at e.useState (react.min.js?ver=17.0.1:9:10899)
at Prompt (Prompt.js:5:35)
at N (element.min.js?ver=3dfdc75a0abf30f057df44e9a39abe5b:2:9552)
at U (element.min.js?ver=3dfdc75a0abf30f057df44e9a39abe5b:2:10502)
at N (element.min.js?ver=3dfdc75a0abf30f057df44e9a39abe5b:2:9284)
at lr (blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:111294)
at blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:137935
at xn (blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:138073)
at blocks.min.js?ver=658a51e7220626e26a92a46af5c2e489:3:139086
The component:
import React, { useState, useEffect } from "react";
import axios from "axios";
function Prompt(props) {
const [data, setData] = useState({ hits: [] });
useEffect(() => {
const fetchData = async () => {
const result = await axios(
"http://my-site-test.local/wp-json/wp/v2/posts?_fields[]=title"
);
setData(result.data);
};
fetchData();
}, []);
console.log(data);
return (
<>
JSX...
</>
);
}
export default Prompt;
I tried to delete node_modules and reinstall to no avail…
I believe the problem is in my-plugin/src/index.js — wp.blocks.registerBlockType's 'save' property only allows static HTML to be returned (so it can be stored in the database within the content) and I was trying to insert a React component into it.
Since I want a dynamic block on the front-end, I have to load a register_block_type in my-plugin/index.php to render my component.
EDIT You actually can add React directly in the save attribute if you have specified script when registering your block in the PHP main file (or in your block.json file.

Next.js dynamic import with custom loading component: "Warning: Expected server HTML to contain a matching <em> in <div>."

I have a next.js page with dynamic imports, which worked well until now,
but since I started using the custom loading components I get the warning
Warning: Expected server HTML to contain a matching <em> in <div>.
What can I do to avoid this warning ?
Here a reduced example:
// pages/index.tsx:
import React from 'react';
import dynamic from 'next/dynamic';
import { LoadingComponent } from '../src/LoadingComponent';
// -- The custom loading fallback component
const loadingFallback = { loading: LoadingComponent };
// -- The dynamically loaded component
const DynamicallyLoadedComponent = dynamic(
() => import('../src/DynamicallyLoadedComponent'),
loadingFallback
);
// -- The main component
function Main(){
return <DynamicallyLoadedComponent />;
}
export default Main;
// src/DynamicallyLoadedComponent.tsx:
export default function DynamicallyLoadedComponent(){
return <div>ready loaded, final page content.</div>;
};
// src/LoadingComponent.tsx:
export const LoadingComponent = () => {
return <em>... loading ...</em>;
}
Apparently you can not extract the part { loading: LoadingComponent } into a variable, so it has to be written
inline, e.g.:
const DynamicallyLoadedComponent = dynamic(
() => import('../src/DynamicallyLoadedComponent'),
{ loading: LoadingComponent } // <-- needs to be written like this, a variable can't be used
);
Maybe this is because Next.js analyses the code in a kind of "pre-compiling" step and expects to find exactly this pattern (?).
Probably this is the same requirement as mentioned under Basic usage regarding the dynamic import itself:
Note: In import('path/to/component'), the path must be explicitly written.
It can't be a template string nor a variable.
Furthermore the import() has to be inside the dynamic() call for Next.js to be able to match
webpack bundles / module ids to the specific dynamic() call and preload them before rendering.
dynamic() can't be used inside of React rendering as it needs to be marked in the top level
of the module for preloading to work, similar to React.lazy.

Next.js withPageAuthRequired with getStaticProps

According documentation #auth0/nextjs-auth0 we can use withPageAuthRequired for trigger login screen on pages required login.
short variant: export const getServerSideProps = withPageAuthRequired();
But what to do if I need to use getStaticProps for pre-render page at build time which can't be used together with getServerSideProps? Is there any way to use withPageAuthRequired on request static generated pages?
Right now I am using double check on client side for check auth. But I would rather use a server side check as i use on other pages.
P.S. There is way to use withPageAuthRequired on client side as well. This is not suitable for my use
Since getStaticProps() is used to build a static page (i.e., no server-side logic/rendering at request time), the auth check and redirect to login will have to happen on the client side.
You might be able to get the behaviour you want by sticking a proxy in front of the static resource (e.g., using Lambda#Edge), though I'm not very familiar with this approach yet.
From your question it sounds like you are already familiar with how to do the check/redirect on the client side, but for the benefit of others who come across this post in the future:
To fetch user information on the client side, add a <UserProvider> to your app, and call the useUser() hook in client-side components.
See docs:
Wrap your pages/_app.js component with the UserProvider component:
// pages/_app.js
import React from 'react';
import { UserProvider } from '#auth0/nextjs-auth0';
export default function App({ Component, pageProps }) {
return (
<UserProvider>
<Component {...pageProps} />
</UserProvider>
);
}
You can now determine if a user is authenticated by checking that the
user object returned by the useUser() hook is defined. You can
also log in or log out your users from the frontend layer of your
Next.js application by redirecting them to the appropriate
automatically-generated route:
// pages/index.js
import { useUser } from '#auth0/nextjs-auth0';
export default function Index() {
const { user, error, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
if (user) {
return (
<div>
Welcome {user.name}!
Logout
</div>
);
}
return Login;
}
For other comprehensive examples, see the EXAMPLES.md
document.
An alternative approach that uses withPageAuthRequired() on the client side:
import React from 'react';
import { withPageAuthRequired } from '#auth0/nextjs-auth0';
import Layout from '../components/layout';
export default withPageAuthRequired(function Profile({ user }) {
return (
<Layout>
<h1>Profile</h1>
<h4>Profile</h4>
<pre data-testid="profile">{JSON.stringify(user, null, 2)}</pre>
</Layout>
);
});
Linked from additional examples.

window not defined while import AgoraRTC from 'agora-rtc-sdk-ng' in nextjs

I get an error window is undefined while importing agora.io.
Kindly import it like shown below
const AgoraRTC = (await import('agora-rtc-sdk-ng')).default
}
AGORA uses "window" object which is a client side object and Next JS renders on server side. So You need to use "dynamic" for client side rendering. Via Dynamic you can import the file on client side so it will work.
You should copy your whole Agora code and put it in a file.
For example in my case I put it in "../components/agoraclientside.js"
So in my index.js file I wrote :
import dynamic from "next/dynamic";
const ClientSideControls = dynamic(
() => {
return import("../components/agoraclientside");
},
{ ssr: false }
);
Then in your return function use <ClientSideControls />

Why is my Stimulus JS controller firing twice?

So, I have a Rails application with webpacker, vue, turbolinks and stimulus js installed.
The problem that I am having is that even though the controller is only imported once and even if I temporarily disable turbolinks the initialize() funcition along with the connect() one get called twice.
This only happens if I do a refresh (i.e. not when I visit the page for the first time, but only if I perform a page reload).
Strangely enough the disconnect() is only getting called once (when I do leave the page)
This sucks because I need to modify the DOM in the initialize, so I get elements added twice.
Does someone have any clue to what's causing this, and / or a solution?
EDIT: application.js as requested
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("channels")
import "stylesheets"
import "controllers"
import "components"
components/index.js
import Vue from 'vue/dist/vue.esm'
import BootstrapVue from 'bootstrap-vue'
import TurbolinksAdapter from 'vue-turbolinks'
Vue.use(BootstrapVue)
Vue.use(TurbolinksAdapter)
const components = {}
const context = require.context("components", true, /_component\.vue$/)
context.keys().forEach(filename => {
const component_name = filename.replace(/^.*[\\\/]/, '').replace(/_component\.vue$/, '')
const component = context(filename).default
components[component_name] = component
})
document.addEventListener('turbolinks:load', () => {
const app = new Vue({
el: '#vueapp',
mounted() {
let input = document.querySelector('[autofocus]');
if (input) {
input.focus()
}
},
components: { ...components }
})
})
controllers/index.js
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"
const application = Application.start()
const context = require.context("controllers", true, /_controller\.js$/)
application.load(definitionsFromContext(context))
in each and every file, if I console.log it gets logged only once...
only in the stimulus controller initialize or connect gets printed twice
this happens only when I reload the page, not when I first visit it.
This is probably due to Turbolinks. When navigating to a new page, Turbolinks first presents a cached version of the page (first stimulus connect) and afterwards replaces the body with the actual requested version of the page (second stimulus connect). See here for more details:
https://mrcodebot.com/turbolinks-calls-stimulus-connect-twice/

Resources