英文:
Next13, different fetching results on server and client
问题
我已经开始使用 Next13 来启动一个项目。一切都进行得很顺利,直到现在。我是一个初学者,我想了解为什么从获取数据时会得到不同的结果。正如你可能知道的,尽管组件被设置为客户端,但它也会在服务器上渲染,这两种渲染应该匹配。
这是获取数据的函数:
export type CountryType = {
name: string;
population: number;
region: string | undefined;
flagUrl: string | undefined;
capital: string | undefined;
}
export default async function fetchData() {
return fetch('https://restcountries.com/v3.1/all')
.then((response) => response.json())
.then((countries) => {
return countries.map((element: any): CountryType => {
return {
name: element.name.common,
population: element.population,
region: element.region,
flagUrl: element.flags.png,
capital: element.capital && element.capital[0],
}
})
})
}
以下是实际运行获取数据函数的组件 - 当指令 "use client" 丢失时,它可以完美运行,因为它不必在客户端再次获取数据:
'use client';
import CountryInput from '@/ui/CountryInput';
import CountryItem from '@/ui/CountryItem';
import SelectRegion from '@/ui/SelectRegion';
import React, { use } from 'react';
import fetchData, { CountryType } from './fetchData';
export default function Page(): JSX.Element {
const countries: CountryType[] = use(fetchData());
console.log(countries[0]);
return (
<>
<div className='w-full min-w-[315px] h-fit flex flex-col gap-y-12 px-4 items-center md:flex-row md:justify-between md:gap-x-2 md:px-16'>
<CountryInput />
<SelectRegion />
</div>
<div className='w-full min-w-[360px] h-fit flex flex-col gap-y-8 px-14'>
{countries.map((element) => {
return (
<CountryItem
key={element.name}
name={element.name}
population={element.population}
region={element.region}
flagUrl={element.flagUrl}
capital={element.capital}
/>
);
})}
</div>
</>
)
}
正如你可能已经注意到的,这个组件中有一个控制台日志 - 现在我将向你展示在客户端和服务器端分别返回的数据:
服务器端 (在 cmd 中):
{
"name": "Iceland",
"population": 366425,
"region": "Europe",
"flagUrl": "https://flagcdn.com/w320/is.png",
"capital": "Reykjavik"
}
客户端 (浏览器控制台):
{
"name": "Turkey",
"population": 84339067,
"region": "Asia",
"flagUrl": "https://flagcdn.com/w320/tr.png",
"capital": "Ankara"
}
这两者不匹配导致错误不断生成:
警告:在水合期间发生错误。在 <#document> 中,服务器 HTML 被客户端内容替换。
页面运行时间越长,错误就会生成得越多。
我已经在互联网上搜索了解决方案,但没有找到任何能帮助我的东西。
我已经检查了这在不同浏览器、不同方法上的行为。我希望这两个控制台日志是相同的。
英文:
I've started a project using Next13. Everything went fine untill now. I'm beginner and I'd like to discover why I'm getting different results from fetching. As you may know - although the component is set to client, it renders on server as well and both of these two renders should match each other.
There is the fetching function:
export type CountryType = {
name: string
population: number
region: string | undefined
flagUrl: string | undefined
capital: string | undefined
}
export default async function fetchData() {
return fetch('https://restcountries.com/v3.1/all')
.then((response) => response.json())
.then((countries) => {
return countries.map((element: any): CountryType => {
return {
name: element.name.common,
population: element.population,
region: element.region,
flagUrl: element.flags.png,
capital: element.capital && element.capital[0],
}
})
})
}
And here is the component that actually runs the function to fetch data - it works perfectly when the directive 'use client' is lost, 'cause it doesn't have to do fetching once again on client side.
'use client'
import CountryInput from '@/ui/CountryInput'
import CountryItem from '@/ui/CountryItem'
import SelectRegion from '@/ui/SelectRegion'
import React, { use } from 'react'
import fetchData, { CountryType } from './fetchData'
export default function Page(): JSX.Element {
const countries: CountryType[] = use(fetchData())
console.log(countries[0])
return (
<>
<div className='w-full min-w-[315px] h-fit flex flex-col gap-y-12 px-4 items-center md:flex-row md:justify-between md:gap-x-2 md:px-16'>
<CountryInput />
<SelectRegion />
</div>
<div className='w-full min-w-[360px] h-fit flex flex-col gap-y-8 px-14'>
{countries.map((element) => {
return (
<CountryItem
key={element.name}
name={element.name}
population={element.population}
region={element.region}
flagUrl={element.flagUrl}
capital={element.capital}
/>
)
})}
</div>
</>
)
}
As you may have noticed, there is a console log in this component - now I'm going to present to you what it returns on both client and server:
Server (in cmd):
{
name: 'Iceland',
population: 366425,
region: 'Europe',
flagUrl: 'https://flagcdn.com/w320/is.png',
capital: 'Reykjavik'
}
Client (browser's console):
{
"name": "Turkey",
"population": 84339067,
"region": "Asia",
"flagUrl": "https://flagcdn.com/w320/tr.png",
"capital": "Ankara"
}
The fact that these two things don't match each other causes the error generating endlessly:
Warning: An error occurred during hydration. The server HTML was replaced with client content in <#document>.
The longer page with this error runs, the more errors are generated.
I've searched on the Internet for the solution, I haven't found anything that could help me.
I've checked how this behaves on different browsers, different approches.
I expect these two console logs to be the same.
答案1
得分: 2
这是代码的翻译部分:
The issue is not related to a browser. According to [Data Fetching: use in Client Components][1] you should not use `use` with `fetch` or it will cause re-renders and unwanted results.
The problem is this attempted async call in your client component:
`const countries: CountryType[] = use(fetchData())`
If you want React to load the async call when the component mounts, you can simply use a `useEffect(() => [])` to run an async call on mount.
They also recommend using a third-party library such as [SWR][2] or [React Query][3].
I used your code in a barebones Next.js 13 app to demonstrate that both `client` and `server` components show the same first country using the same end-point.
Here's a [working sandbox][4] that uses your code modified to demonstrate this.
For example inside of a **Next.js 13** app:
**page.tsx**
import ClientFetch from "./ClientFetch"
import ServerFetch from "./ServerFetch"
const App = async () => {
return (
<>
<ClientFetch />
<ServerFetch />
</>
);
};
export default App;
**ServerFetch.tsx**
async function getData() {
const res = await fetch("https://restcountries.com/v3.1/all");
if (!res.ok) {
throw a Error("Failed to fetch data");
}
return res.json();
}
const ServerFetch: any = async () => {
const data = await getData();
const countries = data.map((c: any) => {
return {
name: c.name.common,
population: c.population,
region: c.region,
flagUrl: c.flags.png,
capital: c.capital && c.capital[0],
};
});
console.log("--- ServerFetch ---", countries[0]);
return (
<main>
<h1>SERVER FETCH</h1>
{[countries[0]].map((c: any, key: number) => (
<div key={key}>
<h3>{c.name}</h3>
<p>{c.population}</p>
<p>{c.region}</p>
<p>{c.flagUrl}</p>
<p>{c.capital}</p>
</div>
))}
</main>
);
};
export default ServerFetch;
**ClientFetch.tsx**
"use client"
import { useState, useEffect } from "react"
// export default async function
const ClientFetch = () => {
const [countries, setCountries] = useState([]);
useEffect(() => {
(async () => {
try {
const res = await fetch("https://restcountries.com/v3.1/all");
const data = await res.json();
setCountries(
data.map((c: any) => {
return {
name: c.name.common,
population: c.population,
region: c.region,
flagUrl: c.flags.png,
capital: c.capital && c.capital[0],
};
})
);
console.log("--- ClientFetch ---", data[0]);
} catch (e) {
console.log(e);
}
})();
}, []);
return (
<main>
<h1>CLIENT FETCH</h1>
{countries.length
? [countries[0]].map((c: any, key: number) => (
<div key={key}>
<h3>{c.name}</h3>
<p>{c.population}</p>
<p>{c.region}</p>
<p>{c.flagUrl}</p>
<p>{c.capital}</p>
</div>
))
: null}
</main>
);
};
export default ClientFetch;
请注意,代码中的HTML标签和特殊字符已经被转义。如果您需要进一步的翻译或解释,请随时提出。
英文:
The issue is not related to a browser. According to Data Fetching: use in Client Components you should not use use
with fetch
or it will cause re-renders and unwanted results.
The problem is this attempted async call in your client component:
const countries: CountryType[] = use(fetchData())
If you want React to load the async call when the component mounts, you can simply use a useEffect(() => [])
to run an async call on mount.
They also recommend using a third-party library such as SWR or React Query.
I used your code in a barebones Next.js 13 app to demonstrate that both client
and server
components show the same first country using the same end-point.
Here's a working sandbox that uses your code modified to demonstrate this.
For example inside of a Next.js 13 app:
page.tsx
import ClientFetch from "./ClientFetch";
import ServerFetch from "./ServerFetch";
const App = async () => {
return (
<>
<ClientFetch />
<ServerFetch />
</>
);
};
export default App;
ServerFetch.tsx
async function getData() {
const res = await fetch("https://restcountries.com/v3.1/all");
if (!res.ok) {
throw new Error("Failed to fetch data");
}
return res.json();
}
const ServerFetch: any = async () => {
const data = await getData();
const countries = data.map((c: any) => {
return {
name: c.name.common,
population: c.population,
region: c.region,
flagUrl: c.flags.png,
capital: c.capital && c.capital[0],
};
});
console.log("--- ServerFetch ---", countries[0]);
return (
<main>
<h1>SERVER FETCH</h1>
{[countries[0]].map((c: any, key: number) => (
<div key={key}>
<h3>{c.name}</h3>
<p>{c.population}</p>
<p>{c.region}</p>
<p>{c.flagUrl}</p>
<p>{c.capital}</p>
</div>
))}
</main>
);
};
export default ServerFetch;
ClientFetch.tsx
"use client";
import { useState, useEffect } from "react";
// export default async function
const ClientFetch = () => {
const [countries, setCountries] = useState([]);
useEffect(() => {
(async () => {
try {
const res = await fetch("https://restcountries.com/v3.1/all");
const data = await res.json();
setCountries(
data.map((c: any) => {
return {
name: c.name.common,
population: c.population,
region: c.region,
flagUrl: c.flags.png,
capital: c.capital && c.capital[0],
};
})
);
console.log("--- ClientFetch ---", data[0]);
} catch (e) {
console.log(e);
}
})();
}, []);
return (
<main>
<h1>CLIENT FETCH</h1>
{countries.length
? [countries[0]].map((c: any, key: number) => (
<div key={key}>
<h3>{c.name}</h3>
<p>{c.population}</p>
<p>{c.region}</p>
<p>{c.flagUrl}</p>
<p>{c.capital}</p>
</div>
))
: null}
</main>
);
};
export default ClientFetch;
Results:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论