Next.js Discord

Discord Forum

Only plain objects can be passed to Client Components from Server Components. Decimal objects not

Answered
Amorph posted this in #help-forum
Open in Discord
Avatar
Image
Answered by Arinji
id: product?.id ?? "",
View full answer

104 Replies

Avatar
I have a products form where i can edit or add a new product.

When I want to go a single product page to edit it, i get this warning in console:

Warning: Only plain objects can be passed to Client Components from Server Components. Decimal objects are not supported.
  {id: ..., storeId: ..., categoryId: ..., name: ..., price: Decimal, isFeatured: ..., isArchived: ..., sizeId: ..., colorId: ..., createdAt: ..., updatedAt: ..., images: ...}
Product page is:
import ProductsForm from "@/components/products-form";
import {
  getCategories,
  getColors,
  getProductWithOnlyImagesRelation,
  getSizes,
} from "@/server/data-access-layer";

export default async function ProductPage({
  params,
}: {
  params: { productId: string; storeId: string };
}) {
  const product = await getProductWithOnlyImagesRelation(params.productId);

  const formattedProduct = {
    ...product,
    price: product?.price.toNumber(),
  };

  const categories = await getCategories(params.storeId);

  const sizes = await getSizes(params.storeId);

  const colors = await getColors(params.storeId);
  return (
    <div className="flex-col">
      <div className="flex-1 space-y-4 p-8 pt-6">
        <ProductsForm
          categories={categories}
          colors={colors}
          sizes={sizes}
          initialData={formattedProduct}
        />
      </div>
    </div>
  );
}
Is there a better way than formatting the product and attaching it in the initialData prop instead of the normal product?
If I use formattedProduct instead, I get this from initial data prop:
Type '{ price: number | undefined; images?: { id: string; productId: string; url: string; createdAt: Date; updatedAt: Date; }[] | undefined; id?: string | undefined; storeId?: string | undefined; ... 7 more ...; updatedAt?: Date | undefined; }' is not assignable to type '{ id: string; storeId: string; categoryId: string; name: string; price: Decimal; isFeatured: boolean; isArchived: boolean; sizeId: string; colorId: string; createdAt: Date; updatedAt: Date; } & { ...; }'.
  Type '{ price: number | undefined; images?: { id: string; productId: string; url: string; createdAt: Date; updatedAt: Date; }[] | undefined; id?: string | undefined; storeId?: string | undefined; ... 7 more ...; updatedAt?: Date | undefined; }' is not assignable to type '{ id: string; storeId: string; categoryId: string; name: string; price: Decimal; isFeatured: boolean; isArchived: boolean; sizeId: string; colorId: string; createdAt: Date; updatedAt: Date; }'.
    Types of property 'id' are incompatible.
      Type 'string | undefined' is not assignable to type 'string'.
        Type 'undefined' is not assignable to type 'string'.ts(2322)
types.ts(169, 3): The expected type comes from property 'initialData' which is declared here on type 'IntrinsicAttributes & ProductFormProps'
Avatar
bump
Avatar
bump
Avatar
bump
Avatar
bump
Avatar
bump
Avatar
bump
Avatar
wow props for following the rules ngl
cant you just use Numbers instead of.. Decimal?
Or if you need to use Decimal, the issue is that Decimals cant be serialized in js atm
so pass it as a string to the client component
decimalValue.toString()
then you can convert back like this
const decimal = new Decimal(decimalAsString);
@Amorph
Avatar
yes i know, hence why i formatted my product to number
import ProductsForm from "@/components/products-form";
import {
  getCategories,
  getColors,
  getProductWithOnlyImagesRelation,
  getSizes,
} from "@/server/data-access-layer";

export default async function ProductPage({
  params,
}: {
  params: { productId: string; storeId: string };
}) {
  const product = await getProductWithOnlyImagesRelation(params.productId);

  const formattedProduct = {
    ...product,
    price: product?.price.toNumber(),
  };

  const categories = await getCategories(params.storeId);

  const sizes = await getSizes(params.storeId);

  const colors = await getColors(params.storeId);
  return (
    <div className="flex-col">
      <div className="flex-1 space-y-4 p-8 pt-6">
        <ProductsForm
          categories={categories}
          colors={colors}
          sizes={sizes}
          initialData={formattedProduct}
        />
      </div>
    </div>
  );
}
Avatar
oh
Avatar
to number or can also do string as u said since those can be passed to client components
but
Avatar
Warning: Only plain objects can be passed to Client Components from Server Components. Decimal objects are not supported.

You dont see this anymore then right?
Avatar
yes i have problem with the types now
if i hover over initialData prop
Type '{ price: number | undefined; images?: { id: string; productId: string; url: string; createdAt: Date; updatedAt: Date; }[] | undefined; id?: string | undefined; storeId?: string | undefined; ... 7 more ...; updatedAt?: Date | undefined; }' is not assignable to type '{ id: string; storeId: string; categoryId: string; name: string; price: Decimal; isFeatured: boolean; isArchived: boolean; sizeId: string; colorId: string; createdAt: Date; updatedAt: Date; } & { ...; }'.
Type '{ price: number | undefined; images?: { id: string; productId: string; url: string; createdAt: Date; updatedAt: Date; }[] | undefined; id?: string | undefined; storeId?: string | undefined; ... 7 more ...; updatedAt?: Date | undefined; }' is not assignable to type '{ id: string; storeId: string; categoryId: string; name: string; price: Decimal; isFeatured: boolean; isArchived: boolean; sizeId: string; colorId: string; createdAt: Date; updatedAt: Date; }'.
Types of property 'id' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.ts(2322)
types.ts(169, 3): The expected type comes from property 'initialData' which is declared here on type 'IntrinsicAttributes & ProductFormProps'
and if i hover over initial data, it will show like this:
Avatar
show me <ProductsForm>
Avatar
(property) initialData: ({
id: string;
storeId: string;
categoryId: string;
name: string;
price: Decimal;
isFeatured: boolean;
isArchived: boolean;
sizeId: string;
colorId: string;
createdAt: Date;
updatedAt: Date;
} & {
...;
}) | null
Image
this is Products form
types are:
export type ProductFormProps = {
  initialData: (Product & { images: Image[] }) | null;
  categories: Category[];
  colors: Color[];
  sizes: Size[];
};
and my product model
model Product {
  id String @id @default (uuid())
  storeId String
  store Store @relation("StoreToProduct", fields: [storeId], references: [id])
  categoryId String
  category Category @relation("CategoryToProduct", fields: [categoryId], references: [id])
  name String
  price Decimal
  isFeatured Boolean @default(false)
  isArchived Boolean @default(false)
  sizeId String
  size Size @relation(fields: [sizeId],references: [id])
  colorId String
  color Color @relation(fields: [colorId], references: [id])
  images Image[]
  orderItems OrderItem[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([storeId])
  @@index([categoryId])
  @@index([sizeId])
  @@index([colorId])
}
Avatar
chill
lol
Avatar
in case ur next questions would be how props look lol
Avatar
its an easy fix, so basically
const product = await getProductWithOnlyImagesRelation(params.productId);
hover over product
and tell me what you see for the type of id
Avatar
string
or u mean type of price?
Avatar
just string or string | undefined
Avatar
just string
const product: ({
images: {
id: string;
productId: string;
url: string;
createdAt: Date;
updatedAt: Date;
}[];
} & {
id: string;
storeId: string;
categoryId: string;
name: string;
price: Decimal;
isFeatured: boolean;
isArchived: boolean;
sizeId: string;
colorId: string;
createdAt: Date;
updatedAt: Date;
}) | null
thats how it looks
Avatar
createdAt: Date; updatedAt: Date; }'.
Types of property 'id' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.ts(2322)
one of your ids are undefined
real quick, paste this
  const formattedProduct = {
    ...product,
    id: product?.id ?? "",
    price: product?.price.toNumber(),
  };
Avatar
now it continues to the next one
Types of property 'storeId' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.ts(2322)
Avatar
but see the issue now?
cause like
product
can be null
got that right @Amorph
Avatar
yes
Avatar
now null isnt an object, it dosent have properties. hence why null.id is undefined
you need to add a like checker for it.
Avatar
if there is initial data then it will use initial data
but it can be null
Avatar
mhm
Avatar
so basically ur saying to format all the fields
Avatar
yes.
Avatar
like i have price/id, but do for all
Avatar
yes.
Avatar
id: product?.id ?? "",
Answer
Avatar
if the id exists, its gonna use it.. if its undefined.. it will use ""
Avatar
ok, let me do this and get back to u
Avatar
sure
this is why we love typescript ❤️
Avatar
@Arinji ok now i got this
Types of property 'price' are incompatible.
Type 'number' is not assignable to type 'Decimal'.ts(2322)
const formattedProduct = {
...product,
id: product?.id ?? "",
storeId: product?.storeId ?? "",
categoryId: product?.categoryId ?? "",
name: product?.name ?? "",
price: product?.price.toNumber() ?? 0,
};
Avatar
price: Decimal;
price: product?.price.toNumber(),
see the issue? :D
Avatar
if im understanding right, im passing a number, but my product form props are using the ProductFormProps types
and initial data type is made of Product model which has price: decimal
so im not sure to go around this, since i want to keep decimal in my model
Avatar
make initial data accept a Number
then convert it back to Decimal
once you are in the client component
Avatar
initialData: (Product & { images: Image[] }) | null;
@Arinji u mean this one right?
and instead of "Product" ill just write the fields myself
and have the price be number not decimal
Avatar
yea
Avatar
@Arinji when it comes to type Date
what would the empty value be here?
as in, for strings its ""
but what is it for a Date type?
Avatar
new Date(0) to set it to unix epoch or new Date() ?
Avatar
Set Dates to strings by .getTime()
It will return back in ms
As a string
Avatar
@Arinji like this? createdAt: product?.createdAt.getTime().toString() ?? "",
because they will still be incompatible
Avatar
actually found much easier way:D instead of accounting for all the variables, i just conditionally format my product only if i already have some data in it
const formattedProduct = product
    ? {
        ...product,
        price: product?.price.toNumber() ?? 0,
      }
    : null;
thanks for all ur help nonetheless
Avatar
Sure, make your types as needed