I'm trying to run a custom document as it says into nextjs docs,
but the app throws an error when I run it. The error is
TypeError: Cannot destructure property 'docComponentsRendered' of '(0 , _react).useContext(...)' as it is null.
enter image description here
Anyway, this is my custom document code:
import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext,
DocumentInitialProps
} from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(
ctx: DocumentContext
): Promise<DocumentInitialProps> {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />)
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
)
}
} finally {
sheet.seal()
}
}
render() {
return (
<Html lang="pt-br">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
I'm running nextjs 13, and trying to configure the styled component.
Could some help me to solve this error?
I already tried to delete the _document, but now the error persists, and I have no idea what is the problem.
Related
I am trying to convert my existing code from Next js 12 to 13. But I am unable to make it work. How should I transform to make it work? Getting errors like you can't use "useState" etc.
import { useState } from 'react';
import Head from 'next/head';
import { loadData } from './api/post';
import {
Section,
Cover,
SocialNetworks,
BuyMeCoffee,
Title,
PostGrid,
Post,
Button
} from '../components';
const LOAD_MORE_STEP = 4;
export default function Home({ initialPosts, total }) {
const [ posts, setPosts ] = useState(initialPosts);
const [ loadedAmount, setLoadedAmount ] = useState(LOAD_MORE_STEP);
const [ loading, setLoading ] = useState(false);
const showLoadButton = total > loadedAmount;
const getMorePosts = async () => {
setLoading(true);
try {
const data = await fetch(`/api/post?start=${loadedAmount}&end=${loadedAmount + LOAD_MORE_STEP}`).then((response) => response.json());
setLoadedAmount(loadedAmount + LOAD_MORE_STEP);
setPosts([...posts, ...data.posts])
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
}
};
return (
<div style={{
marginBottom: '1rem',
}}>
<Head>
<title>My blog</title>
</Head>
<Section>
<Cover title="Elena<br />Litvinova" />
<SocialNetworks />
<BuyMeCoffee />
</Section>
<Section>
<Title>New Post</Title>
<PostGrid>
{posts.map((post) => (
<Post
key={post._id}
{...post}
/>
))}
</PostGrid>
{showLoadButton && (
<div style={{
display: 'flex',
justifyContent: 'center',
}}>
<Button
disabled={loading}
onClick={getMorePosts}
>
Load more posts...
</Button>
</div>
)}
</Section>
</div>
)
}
export async function getServerSideProps() {
const { posts, total } = await loadData(0, LOAD_MORE_STEP);
return {
props: {
initialPosts: posts,
total
},
}
}
React hooks are only available on the client. You have to add 'use client' to the top of your file, if it is a client component. In Next.js 13 all components are React server component by default. Here are the docs for server and client components in Next.js
I would also recommend you to fetch your data with #tanstack/react-query.
I tried to implement a simple UI for smart contracts using polkadot.js in the next.js framework.
The content of the WEB UI is a simple one that calls the Flipper contract, which is famous for the sample contract of the substrate.
When compiling, the following error is output. Can you tell me how to solve it?
Souce Code:
import { useEffect, useState } from "react";
import {
web3Accounts,
web3Enable,
web3FromSource,
} from "#polkadot/extension-dapp";
import { InjectedAccountWithMeta } from "#polkadot/extension-inject/types";
const Home = () => {
const [allAccount, setAllAccount] = useState<InjectedAccountWithMeta[]>([]);
const getAccounts = async () => {
const extensions = await web3Enable("my cool dapp");
if (extensions.length === 0) {
return;
}
const allAccounts = await web3Accounts();
setAllAccount(allAccounts);
};
useEffect(() => {
getAccounts();
}, []);
return (
<>
<div>
{typeof allAccount !== "undefined"
? allAccount.map((account) => {
return (
<div key={account.address}>
<div className="font-bold mb-2 text-white">
{account.address}
</div>
</div>
);
})
: ""}{" "}
</div>
</>
);
};
export default Home;
Error Information:
> Build error occurred
ReferenceError: window is not defined
at file:///Users/shin.takahashi/develop/substrate/flipper_frontend/fillper_frontend/node_modules/#polkadot/extension-dapp/bundle.js:10:13
at ModuleJob.run (node:internal/modules/esm/module_job:175:25)
at async Loader.import (node:internal/modules/esm/loader:178:24)
at async importModuleDynamicallyWrapper (node:internal/vm/module:437:15) {
type: 'ReferenceError'
}
This issue occurs because next.js is a framework for server-side rendering.
In order to avoid this problem, it is necessary to control not to execute
server-side rendering for the relevant part.
Componentize the part that gets account information from the relevant Extension.
Adopt dynamic import when using this component and set server-side rendering to off.
component sample code:
import { useEffect, useState } from "react";
import { web3Accounts, web3Enable } from "#polkadot/extension-dapp";
import { InjectedAccountWithMeta } from "#polkadot/extension-inject/types";
const Extention = () => {
const [allAccount, setAllAccount] = useState<InjectedAccountWithMeta[]>([]);
const getAccounts = async () => {
const extensions = await web3Enable("my cool dapp");
if (extensions.length === 0) {
return;
}
const allAccounts = await web3Accounts();
setAllAccount(allAccounts);
};
useEffect(() => {
getAccounts();
}, []);
return (
<>
<div>
{typeof allAccount !== "undefined"
? allAccount.map((account) => {
return (
<div key={account.address}>
<div className="font-bold mb-2 text-white">
{account.address}
</div>
</div>
);
})
: ""}{" "}
</div>
</>
);
};
export default Extention;
Calling component sample code:
import dynamic from "next/dynamic";
import { useState } from "react";
const Extention = dynamic(() => import("../component/extention"), {
ssr: false,
});
const Home = () => {
const [showExtention, setShowExtention] = useState(false);
return (
<>
<button onClick={() => setShowExtention(true)}>show extention</button>
{showExtention == true && <Extention></Extention>}
</>
);
};
export default Home;
I have a nextJS site that has a custom _app.js file with the following code.
I have dynamic content from a cms that i use getServerSide props from for each of the pages I have.
const MyApp = ({ Component, pageProps }) => {
const { global } = pageProps
if (global == null) {
return <ErrorPage statusCode={404} />
}
if(global.attributes === undefined){
return <Component />
}
const { metadata, favicon, metaTitleSuffix } = global.attributes
return (
<>
{/* Favicon */}
<Head>
<link
rel="shortcut icon"
href={getStrapiMedia(favicon.data.attributes.url)}
/>
</Head>
{/* Global site metadata */}
<DefaultSeo
titleTemplate={`%s | ${metaTitleSuffix}`}
title="Page"
description={metadata.metaDescription}
/>
{/* Display the content */}
<Component {...pageProps} />
</>
)
}
MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext)
const globalLocale = await getGlobalData(appContext.router.locale)
return {
...appProps,
pageProps: {
global: globalLocale,
},
}
}
export default MyApp
Two questions in a way.
A. Do I even need this getInitialProps?
B. Is there a better way of using getInitialProps
Let me know if anymore code is needed
Goal:
For React TS.
The page List1 and List2 should use the same method named useFetch (retrieve data by using api link) by using generic approach by sending the interface (named Client and TheComputer) to the useFetch.
Each interface has different datamember.
You should enable to use useFetch by many and different interface's name.
In other words,
UseFetch should be a independent tool that can be used by different interface by sending api link and interface asa parameter.
Problem:
You are enable to use react js to achieve it (without using syntax interface) but not for React TS.
I have problem to make useFetch as a independent component with react TS. How should it be solved?
Other info:
*It is achieved for ReactJS but not for ReactTS.
*Somehow it doesn't work in my local computer probably due to strictly linting and TS error.
You need to use interface to order to retrieve data and then display it.
*Newbie in ReactTS
Thank you!
Stackblitz:
JS
https://stackblitz.com/edit/react-mjvs38?
TS
https://stackblitz.com/edit/react-ts-7oeqen?
index.tsx
import React, { Component } from 'react';
import { render } from 'react-dom';
import {
BrowserRouter as Router,
Link,
Route,
Routes,
useParams,
} from 'react-router-dom';
import './style.css';
import useFetch1 from './useFetchTS1';
import useFetch2 from './useFetchTS2';
function App() {
return (
<div>
<h1>Home</h1>
</div>
);
}
function List1() {
const { data, loading, error } = useFetch1('https://api.github.com/users');
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<img src={item.avatar_url} />
<div>{item.id}</div>
</div>
))}
;
</div>
);
}
function List2() {
const { data, loading, error } = useFetch2(
'https://jsonplaceholder.typicode.com/todos'
);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<div>
Id: {item.id} Title: {item.title}
</div>
</div>
))}
;
</div>
);
}
render(
<Router>
<div>
<header>
<Link to="/">Home</Link>
<br />
<Link to="/list1">List1</Link>
<br />
<Link to="/list2">List2</Link>
<br />
<hr />
</header>
<Routes>
<Route path="/" element={<App />} exact></Route>
<Route path="/list1" element={<List1 />} exact></Route>
<Route path="/list2" element={<List2 />}></Route>
</Routes>
</div>
</Router>,
document.getElementById('root')
);
useFetchTS1.tsx
import { useState, useEffect } from 'react';
interface Client {
id: number;
avatar_url: string;
}
export default function useFetch1(url) {
const [data, setData] = useState<Client[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
}
useFetchTS2.tsx
import { useState, useEffect } from 'react';
interface TheComputer {
id: number;
title: string;
}
export default function useFetch2(url) {
const [data, setData] = useState<TheComputer[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
}
There is a design that used to be called the Service Agent pattern that may work well for you:
Use React in the standard way, with useEffect etc
Views simply get type safe data and update their model
Views know nothing about APIs and just ask the agent class
The agent class can express the API interface
A lower level fetch class can do plumbing in a shared way
For sonething to compare against, see if any of my code is useful:
View classes
API classes
In these examples:
CompaniesContainer is the view class
ApiFetch sends and receives any type of API payload, and does common tasks such as refreshing OAuth tokens
ApiClient ensures that views use only type safe requests and responses
You can adapt some of this into a React hook if you prefer. Personally though I prefer to limit React syntax to view logic, and use plain Typescript classes in other places. I can then use equivalent classes in other types of UI, such as mobile apps.
So I believe we can get the useFetch hook to be generic for you if we change it to the following:
import { useEffect, useState } from 'react';
export default function useFetch1<TData = any>(url) {
const [data, setData] = useState<TData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
We use a generic <TData = any> in the function definition and TData[] in the useState hook for data.
Then in your index.tsx file you can define the interfaces there, and pass them to the generic useFetch1 hook like this:
useFetch1<Client>('https://api.github.com/users');
and
useFetch1<TheComputer>('https://jsonplaceholder.typicode.com/todos');
This lets you have the useFetch hook be generic, and still get the data returned to be the correct Interface/Type.
Your updated index.tsx file would look like this:
import './style.css';
import {
Link,
Route,
BrowserRouter as Router,
Routes,
useParams,
} from 'react-router-dom';
import React, { Component } from 'react';
import { render } from 'react-dom';
import useFetch1 from './useFetchTS1';
interface Client {
id: number;
avatar_url: string;
}
interface TheComputer {
id: number;
title: string;
}
function App() {
return (
<div>
<h1>Home</h1>
</div>
);
}
function List1() {
const { data, loading, error } = useFetch1<Client>('https://api.github.com/users');
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<img src={item.avatar_url} />
<div>{item.id}</div>
</div>
))}
;
</div>
);
}
function List2() {
const { data, loading, error } = useFetch1<TheComputer>(
'https://jsonplaceholder.typicode.com/todos'
);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<div>
Id: {item.id} Title: {item.title}
</div>
</div>
))}
;
</div>
);
}
render(
<Router>
<div>
<header>
<Link to="/">Home</Link>
<br />
<Link to="/list1">List1</Link>
<br />
<Link to="/list2">List2</Link>
<br />
<hr />
</header>
<Routes>
<Route path="/" element={<App />} exact></Route>
<Route path="/list1" element={<List1 />} exact></Route>
<Route path="/list2" element={<List2 />}></Route>
</Routes>
</div>
</Router>,
document.getElementById('root')
);
This utilizes generic types: https://www.typescriptlang.org/docs/handbook/2/generics.html
I updated the stackblitz too and seems to be working: https://stackblitz.com/edit/react-ts-zasl3r?file=useFetchTS1.tsx
I have a custom _app.js which looks like this:
function MyApp({ Component, pageProps }) {
const Layout = Component.layoutProps?.Layout || React.Fragment
const layoutProps = Component.layoutProps?.Layout
? { layoutProps: Component.layoutProps }
: {}
const meta = Component.layoutProps?.meta || {}
const description =
meta.metaDescription || meta.description || 'Meta Description'
const store = useStore(pageProps.initialReduxState)
return (
<QueryClientProvider client={queryClient}>
<Provider session={pageProps.session}>
<Title suffix="My Dynamic Site">{meta.metaTitle || meta.title}</Title>
<Description>{description}</Description>
<Meta />
<ReduxProvider store={store}>
<PersistGate persistor={store.__PERSISTOR} loading={null}>
<CartBox />
<Layout {...layoutProps}>
<Component {...pageProps} />
</Layout>
</PersistGate>
</ReduxProvider>
<ReactQueryDevtools initialIsOpen={false} />
</Provider>
</QueryClientProvider>
)
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
return { ...appProps }
}
export default MyApp
Now, I would like to fetch data using ssg/ssr data fetching method to help SEO team for my page components.
But, it seems any of the methods aren't working as expected, none of them actually passing props to the page component.
Here's my page component.
const HomePage = ({ title, stars }) => {
console.log(title, stars); // undefined, undefined
return (
<div>
<Header title={title} />
<GhStars stars={stars} />
<Footer />
</div>
)
}
export const getStaticProps = async () => {
return {
props: {
title: "My Dynamic Title From getStaticProps"
}
}
}
// I tried both getInitialProps & getStaticProps independently.
HomePage.getInitialProps = async (ctx) => {
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const json = await res.json()
return { stars: json.stargazers_count }
}
export default HomePage
I might be missing something for sure, which I failed to figure out so far.
Any help will be really much appreciated. Thanks.