đź‘‹ Next.js Conf 2024: Come build, party, run, and connect with us! See all events

Troubleshooting Portable Text implementation in Sanity/Next.js project

5 replies
Last updated: Aug 23, 2024
Hey Team! I am trying to implement Portable Text into my Sanity/Next project. I am using the template template-nextjs-personal-website which uses
_import_ {
PortableText,

_type_ PortableTextBlock,

_type_ PortableTextComponents,
}
_from_ 'next-sanity'

I am stuck with this error and have been triple checking everything, has anyone ran into this?

Error: Objects are not valid as a React child (found: object with keys {markDefs, children, _type, style, _key}). If you meant to render a collection of children, use an array instead.



        defineField({
          name: 'optionalText',
          title: 'Optional Text',
          type: 'array',
          of: [
            defineArrayMember({
              lists: [],
              marks: {
                annotations: [
                  {
                    name: 'link',
                    type: 'object',
                    title: 'Link',
                    fields: [
                      {
                        name: 'href',
                        type: 'url',
                        title: 'Url',
                      },
                    ],
                  },
                ],
                decorators: [
                  {
                    title: 'Italic',
                    value: 'em',
                  },
                  {
                    title: 'Strong',
                    value: 'strong',
                  },
                ],
              },
              styles: [],
              type: 'block',
            }),
          ],
          validation: (rule) => rule.max(155).required(),
        }),
Trying to render

<CustomPortableText
   paragraphClasses="font-serif max-w-3xl text-gray-600 text-xl"
   value={hero.optionalText}
/>
Aug 23, 2024, 8:02 PM
👋 My initial thought is you’re getting that error because you’re not passing a component in to handle your
link
annotation.
Aug 23, 2024, 8:35 PM
Hi
user M
thank you for the quick response! I have a component that I am importing in to handle the link annotations

import {
  PortableText,
  type PortableTextBlock,
  type PortableTextComponents,
} from 'next-sanity'
import type { Image } from 'sanity'

import ImageBox from '@/components/shared/ImageBox'
import { TimelineSection } from '@/components/shared/TimelineSection'

export function CustomPortableText({
  paragraphClasses = 'my-4', // Default paragraph classes
  value,
}: {
  paragraphClasses?: string
  value: PortableTextBlock[] | undefined // Allow for undefined values
}) {
  console.log('CustomPortableText value:', JSON.stringify(value, null, 2))
  if (!Array.isArray(value)) {
    console.error(
      'Invalid value prop passed to CustomPortableText component. Expected an array of PortableTextBlock objects.',
    )
    return null
  }
  const components: PortableTextComponents = {
    block: {
      normal: ({ children }) => {
        if (Array.isArray(children)) {
          return children.map((child, index) => (
            <p key={index} className={paragraphClasses}>
              {child}
            </p>
          ));
        } else {
          return <p className={paragraphClasses}>{children}</p>;
        }
      },
    },
    marks: {
      link: ({ children, value }) => {
        return (
          <a
            className="underline transition hover:opacity-50"
            href={value?.href}
            rel="noreferrer noopener"
          >
            {children}
          </a>
        )
      },
    },
    types: {
      image: ({
        value,
      }: {
        value: Image & { alt?: string; caption?: string }
      }) => {
        if (!value) return null // Return null if value is not defined

        return (
          <div className="my-6 space-y-2">
            <ImageBox
              image={value}
              alt={value.alt || 'Image'} // Default alt text if none is provided
              classesWrapper="relative aspect-[16/9]"
            />
            {value?.caption && (
              <div className="font-sans text-sm text-gray-600">
                {value.caption}
              </div>
            )}
          </div>
        )
      },
      timeline: ({ value }) => {
        const { items } = value || {}
        return <TimelineSection timelines={items || []} /> // Ensure items is defined
      },
    },
  }
  console.log('CustomPortableText value:', JSON.stringify(value, null, 2))
  // Render only if value is defined and is an array
  if (!value || !Array.isArray(value)) return null

  return <PortableText components={components} value={value} />
}
Aug 23, 2024, 8:38 PM
Got it! It doesn’t look like the link is the issue. I can’t tell where it would be coming from, since it’s possible it’s happening anywhere that you’re passing down
child
or
children
, or even in one of the other components you’ve defined. Can you try commenting them out and adding them back one by one to narrow down which is responsible?
Aug 23, 2024, 8:48 PM
I have been trying to get this to work for a couple days, I just reloaded everything and made sure the data in Sanity was still present. The app is now loading and rendering the data, not sure what the error was which is the most frustrating part. BUT I think it could have been the data being null in Sanity after editing the schema..
Anyway, its fixed now thank you for your time
user M
Aug 23, 2024, 8:50 PM
Glad it’s working!
Aug 23, 2024, 8:52 PM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?