Niall
Niall

Reputation: 277

Warning: Only plain objects can be passed to Client Components from Server Components

I have created an app to be able to search care homes on a map and add new ones through a form.

Background: Everything worked in dev mode but in production the map was not refreshing to include the newly added care homes.The api to fetch the care home data was running at build time and after that subsequent requests return the same data ie don't include new data. (I don't understand why that was happening)

As a solution I used a server action to get the carehome data and return it to the client map component.

Now I am getting an warning saying that Only plain objects can be passed to Client Components from Server Components. Objects with toJSON methods are not supported. Convert it manually to a simple value before passing it to props.

If you have any ideas why an api would only run at build time or how I can solve this warning I would really appreciate it. I am relatively beginner with next.js and development in general so apologies if this is not clear.

link to repo: https://github.com/niallam22/lottie/blob/main/src/app/components/careHomeMap.jsx

_actions.js

export async function getCareHomes(){
  try {
    await connectMongoDB();
    const careHomes = await CareHome.find();
    console.log('api/carehome careHomes: ',careHomes);
    // Return the list of care homes in the response
    return careHomes;
  } catch (error) {
    // Handle any errors that occur during the query
    console.error('Error:', error);
    
    // Return an error response
    return { message: 'Error fetching care homes' };
  }
}
'use client'

import { useEffect, useState } from "react";
import { getCareHomes } from '../_actions'


export default function CareHomeMap({ map, home }) {
  const [careHomeData, setCareHomeData] = useState();

  useEffect(()=>{
    try {
        getCareHomes().then(data =>{
          setCareHomeData(data)
        })

        // fetchCareHomes().then(data =>{
        //   setCareHomeData(data)
        // })
    } catch (error) {
      console.log(error)
    }
  },[])

Upvotes: 26

Views: 98051

Answers (16)

CrackerKSR
CrackerKSR

Reputation: 1926

I had this issue due to passing down result of getServerSession() to client component. This happens when there is an error in the method. success result works fine.
I simply created a server side reusable method which handles error and returns required data instead of entire object like session.user or null.

try{
  const session = await getServerSession();
  return {user:session.user}
}catch(...){
  return {user:null, } // or {}
}

Upvotes: 0

Muhammad Raheel
Muhammad Raheel

Reputation: 1

The error occurs because mongoose documents contain methods and complex objects that can't be directly serialized when passing from server to client components. we need to convert the mongoose document to a plain javascript object and ensure the _id is properly stringified.

Here's the solution that works in my case

"use server";
import { Connect } from "@/db/connection";
import { UserModel } from "@/models";

export const registerUser = async (user) => {
  await Connect();
  const newUser = await UserModel.create(user);
  const savedUser = await newUser.save();

  const userData = {
    ...savedUser.toObject(),
    _id: savedUser._id.toString(),
  };

  return {
    message: "User registered successfully",
    data: userData,
  };
};

Upvotes: 0

Deadlykipper
Deadlykipper

Reputation: 878

I realise I'm late to the party. But, in case anyone comes looking who's using a standard fetch and getting this error, I fixed this by adding .json() to the response.

Got the above error when using:

{
    ...
    const response = await fetch(url, options);
    return response as T;
}

Fixed it using:

    ...
    const response = await fetch(url, options).then((res) =>{
      return res.json();
    });
    return response as T;
}

And this is where T is a variety of incredibly complex objects.

Upvotes: 0

Malak Joseph
Malak Joseph

Reputation: 21

This happens when you pass an object with methods from a server to a client component. The values of the object should not contain methods/functions.

Look at this example with iron-session:

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const session = await getSession();
  const {
    destroy: _destroy,
    save: _save,
    updateConfig: _updateConfig,
    ...rest
  } = session;

  return (
    <html>
      <body className={inter.className}>
        <AuthProvider session={rest}>
          <Navbar />
          <main>{children}</main>
          <Toaster />
        </AuthProvider>
      </body>
    </html>
  );
}

Here I'm removing the methods inside the session object usually used to control the session.

It's just you need to purge the passed object from methods in one way or another...

Upvotes: 1

MUHAMMAD MESUM
MUHAMMAD MESUM

Reputation: 74

Not sure if this question is still relevant to somebody but I have a short helper function to cop with this issue

export async function sanitizeData(data:any) {
return JSON.parse(JSON.stringify(data));
}

and using it as follow

studentDetails =  await sanitizeData(studentDetails);

I initially had this issue while using prisma raw query at the backend and on frontend I have to sanatice the data

Upvotes: 0

Abhishek Prasad
Abhishek Prasad

Reputation: 1

This worked for me: The Query.prototype.lean() function returns the documents from queries with the lean option enabled as plain JavaScript objects, not Mongoose Documents read more about .lean().

And try this out in your code:

const careHomes = await CareHome.find().lean()

Upvotes: 0

amir rasooli
amir rasooli

Reputation: 489

you should before passing data to page for use data, convert to string and so to json

  const data = JSON.parse(JSON.stringify(sampleData))

Upvotes: 48

Chen W
Chen W

Reputation: 319

If you're running into this while using React Query + Server Actions.

Instead of this:

const { data: subscriptionInfo } = useQuery(QUERY_KEYS.subscription, 
  fetchSubscriptionInfo
);

You may wanna do:

  const { data: subscriptionInfo } = useQuery(QUERY_KEYS.subscription, () => {
    return fetchSubscriptionInfo();
  });

fetchSubscriptionInfo is a Server Action.

The reason is similar to the event handling ones above. Although I believe the first one will still work despite the error, since fetchSubscriptionInfo does not take in any params. Basically React Query is trying to pass a complex param(not form/object) to the Server Action, and React lets you know that it can't parse it.

Upvotes: 2

Archibong Samuel
Archibong Samuel

Reputation: 1

I had a similar issue when and it brought me to this article, which also gave me a solution.

In your case, you are passing a server component to a client component, which can only accept plain objects. In your case, careHomes that you are getting from mongoDB is not a plain object. In my case, I'm using sanity.

The client component can not read API data due to its format.

In my case, I wrote my API route as you did but I have an action that calls the data into the client component but does not return the data as a response.json().

So the solution to this problem is response.json()

Upvotes: 0

Ni Ma
Ni Ma

Reputation: 51

make sure you are using 'use server' in your server component.

Upvotes: 0

&#193;ngel De La Cruz
&#193;ngel De La Cruz

Reputation: 810

Server Actions:

export const deleteCompleted = async (): Promise<void> => {
  await prisma.todo.deleteMany({
    where: { complete: true },
  });
  revalidatePath("/dashboard/server-todos");
};

Frontend:

<button
    onClick={() => {
      deleteCompleted();
    }}
    type="button"
    className="flex items-center justify-center rounded ml-2 bg-red-400 p-2 text-white hover:bg-red-700 transition-all"
  >
    <IoTrashOutline />
    Borrar completados
  </button>

Upvotes: 0

Kevin
Kevin

Reputation: 1377

I encountered this error because I was passing the reference of a server function to an onClick handler. Passing the reference of the function to the handler makes sure the event handler parameters are passed to the server function and that doesn't seem to be allowed.

onClick={revalidateEvents}

throws the error because this essentially equals:

onClick={(event) => {revalidateEvents(event)}}

So the solution for me was

onClick={() => {revalidateEvents()}}

Upvotes: 12

Syre Musk
Syre Musk

Reputation: 81

For me, I encountered this same error because I had mistakenly named a file under NextJs api folder as page.ts instead of route.ts

Upvotes: 1

ttt
ttt

Reputation: 6829

I'm assuming that your server action returns ("careHomes") an unsupported type.

The arguments and return value of Server Actions must be "React serializable values": https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#behavior

The supported types for Server Action arguments:

Primitives

  • string
  • number
  • bigint
  • boolean
  • undefined
  • null
  • symbol, only symbols registered in the global Symbol registry via Symbol.for

Iterables containing serializable values

  • String

  • Array

  • Map

  • Set

  • TypedArray and ArrayBuffer

  • Date

  • FormData instances

  • Plain objects: those created with object initializers, with serializable properties

  • Functions that are Server Actions

  • Promises

Notably, these are not supported:

  • React elements, or JSX
  • Functions, including component functions or any other function that is not a Server Action
  • Classes
  • Objects that are instances of any class (other than the built-ins mentioned) or objects with a null prototype
  • Symbols not registered globally, ex. Symbol('my new symbol') Supported serializable return values are the same as serializable props for a boundary Client Component.

Source: https://react.dev/reference/react/use-server#serializable-parameters-and-return-values

Upvotes: 2

I had the same issue and it was caused by _id from MongoDB not being a plain object, I have used toString() and it works.

Upvotes: 0

shirin
shirin

Reputation: 192

This is because you are passing a server component to a client component and client component can only accept plain objects. In your case, careHomes that you are getting from mongoDB is not a plain object. Documents coming from MongoDB has this ._id which is a reference to a complex object. If you print out your return from mongoDB you'll see something like this(_id):

_id: new ObjectId('65aed9609c564bf65becf14a'),
  title: 'User Interface Enhancement',
  description: ' Improve the user interface of the application to enhance user experience and modernize the overall look and feel.',
  category: 'Projects',
  priority: 4,
  progress: 28,

I can recommend you 2 solutions:

  1. use .toString()
careHomes._id = careHomes._id.toString()
  1. Use a template string to pass the ._id
`${careHomes._id}`

Upvotes: 4

Related Questions