如何创建一个能够在 NextJS 13 中获取数据的简单服务器组件?

huangapple go评论60阅读模式
英文:

How do I create a simple server component that can fetch data in NextJS 13?

问题

I am learning NextJS. Currently, I am using NextJS 13 with the new app directory instead of the old pages directory. I have tried various ways of fetching data, but I have not gotten any of them to work. It should be as simple as

src/app/page.tsx:

export interface ToDo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

const getToDos = async (): Promise<ToDo[]> => {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  console.log(`Status: ${res.status}`); // prints 'Status: 200'
  return res.json();
}

const Home = async () => {
  const toDos = await getToDos();
  return (
    <ul>
      {toDos.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

export default Home;

The JSON returned by the endpoint is

{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

This is the output. Notice how I am getting both a 200 OK status and an error message:

Warning: Only plain objects can be passed to Client Components from Server Components. Classes or other objects with methods are not supported.

Status: 200

-  ┌ GET / 200 in 606ms
   │
   └──── GET https://jsonplaceholder.typicode.com/todos/1 200 in 67ms (cache: HIT)

- error node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js (1863:12) @ resolveModelToJSON
- error Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".

- error node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js (1863:12) @ resolveModelToJSON
- error Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".

If I add "use server" to the beginning of page.tsx, I get a compilation error:

Server Actions require `experimental.serverActions` option to be enabled in your Next.js config: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions

Ideally, I would like to statically generate HTML, but for now, I would be grateful for any working fetching code.

Update

One of the previous solutions I tried was to use Tanstack Query. I have now removed all of that code from my layout.tsx. Now I get a different error for this code:

export default async function Home(): Promise<JSX.Element> {
  const todos = await getToDos();
  console.log(todos);
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

saying

error TypeError: todos.map is not a function
英文:

I am learning NextJS. Currently, I am using NextJS 13 with the new app directory instead of the old pages directory. I have tried various ways of fetching data, but I have not gotten any of them to work. It should be as simple as

src/app/page.tsx:

export interface ToDo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

const getToDos = async (): Promise&lt;ToDo[]&gt; =&gt; {
  const res = await fetch(&#39;https://jsonplaceholder.typicode.com/todos/1&#39;);
  console.log(`Status: ${res.status}`); // prints &#39;Status: 200&#39;
  return res.json();
}

const Home = async () =&gt; {
  const toDos = await getToDos();
  return (
    &lt;ul&gt;
      {toDos.map((todo) =&gt; (
        &lt;li key={todo.id}&gt;{todo.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}

export default Home;

The JSON returned by the endpoint is

{
  &quot;userId&quot;: 1,
  &quot;id&quot;: 1,
  &quot;title&quot;: &quot;delectus aut autem&quot;,
  &quot;completed&quot;: false
}

This is the output. Notice how I am getting both a 200 OK status and an error message:

Warning: Only plain objects can be passed to Client Components from Server Components. Classes or other objects with methods are not supported.
  &lt;... client={{queryCache: ..., mutationCache: ..., logger: ..., defaultOptions: ..., queryDefaults: ..., mutationDefaults: ..., mountCount: ...}} children=...&gt;
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Warning: Only plain objects can be passed to Client Components from Server Components. Classes or other objects with methods are not supported.
  {queryCache: {listeners: Set, subscribe: function, config: ..., queries: ..., queriesMap: ...}, mutationCache: ..., logger: ..., defaultOptions: ..., queryDefaults: ..., mutationDefaults: ..., mountCount: ...}
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Status: 200

-  ┌ GET / 200 in 606ms
   │
   └──── GET https://jsonplaceholder.typicode.com/todos/1 200 in 67ms (cache: HIT)

- error node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js (1863:12) @ resolveModelToJSON
- error Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with &quot;use server&quot;.
  {listeners: Set, subscribe: function, config: ..., queries: ..., queriesMap: ...}
                              ^^^^^^^^
    at stringify (&lt;anonymous&gt;)
null
- error node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js (1863:12) @ resolveModelToJSON
- error Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with &quot;use server&quot;.
  {listeners: Set, subscribe: function, config: ..., queries: ..., queriesMap: ...}
                              ^^^^^^^^
    at stringify (&lt;anonymous&gt;)
digest: &quot;501055159&quot;
null

If I add "use server" to the beginning of page.tsx, I get a compilation error:

Server Actions require `experimental.serverActions` option to be enabled in your Next.js config: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions

Ideally I would like to statically generate HTML, but for now I would be grateful for any working fetching code.

Update

One of the previous solutions I tried was to use Tanstack Query. I have now removed all of that code from my layout.tsx. Now I get a different error for this code:

export default async function Home(): Promise&lt;JSX.Element&gt; {
  const todos = await getToDos();
  console.log(todos);
  return (
    &lt;ul&gt;
      {todos.map((todo) =&gt; (
        &lt;li key={todo.id}&gt;{todo.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}

saying

error TypeError: todos.map is not a function

答案1

得分: 1

似乎您正在尝试将一个来自服务器组件的函数传递给客户端组件,除非它是一个服务器动作,否则这是不可能的,因为服务器动作目前仍处于 alpha 版本,您需要通过在您的 next.config.js 文件中设置 experimental.serverActions 来启用它们:

module.exports = {
  experimental: {
    serverActions: true,
  },
};

这是一个服务器端组件的示例:

import "server-only";

interface ToDo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

async function getToDos(): Promise<ToDo[]> {
  const res = await fetch("https://jsonplaceholder.typicode.com/todos");
  return await res.json(); // 顺便提一下,您漏掉了一个 await
}

export default async function Home(): Promise<JSX.Element> {
  const todos = await getToDos();
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

我强烈建议您安装 server-only 包,这样如果您尝试在客户端导入服务器组件,就会抛出错误。

英文:

It seems you are trying to pass a function from a server component to a client component, this is not possible unless it is a server action, since server actions are still a feature currently in alpha, you need to enable them by setting experimental.serverActions in your next.config.js file:

module.exports = {
  experimental: {
    serverActions: true,
  },
};

Here is an example of a server side component:

import &quot;server-only&quot;;

interface ToDo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

async function getToDos(): Promise&lt;ToDo[]&gt; {
  const res = await fetch(&quot;https://jsonplaceholder.typicode.com/todos&quot;);
  return await res.json(); // by the way, you were missing an await here
}

export default async function Home(): Promise&lt;JSX.Element&gt; {
  const todos = await getToDos();
  return (
    &lt;ul&gt;
      {todos.map((todo) =&gt; (
        &lt;li key={todo.id}&gt;{todo.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}

I strongly recommend you install the server-only package, so an error is thrown if you try to import a server component into the client side.

答案2

得分: 0

正如 @FabioNettis 正确指出的那样,我尝试获取一个单一对象,这不可迭代。我意外地尝试从路径 /todos/1 而不是 /todos 获取。因此,获取代码实际上是正确的,尽管不是很好。良好的错误处理会更容易调试。

英文:

As @FabioNettis correctly pointed out, I was trying to fetch a single object, which is not iterable. I had accidentally tried to fetch from the path /todos/1 rather than /todos. So the fetching code was actually correct, although not great. Good error handling would have made debugging easier.

huangapple
  • 本文由 发表于 2023年6月15日 20:12:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/76482331.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定