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.
<... client={{queryCache: ..., mutationCache: ..., logger: ..., defaultOptions: ..., queryDefaults: ..., mutationDefaults: ..., mountCount: ...}} children=...>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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 "use server".
{listeners: Set, subscribe: function, config: ..., queries: ..., queriesMap: ...}
^^^^^^^^
at stringify (<anonymous>)
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 "use server".
{listeners: Set, subscribe: function, config: ..., queries: ..., queriesMap: ...}
^^^^^^^^
at stringify (<anonymous>)
digest: "501055159"
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<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
>Solution :
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 "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(); // by the way, you were missing an await here
}
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>
);
}
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.