Migrating from react-portable-text to @portabletext/react value prop

28 replies
Last updated: Jan 24, 2026
Currently using the older PortableText plugin react-portable-text but I would like to migrate to _*@portabletext/react.* Doing so, it’s asking to add a value instead of content. using the older plugin I specify in Nextjs the content from the post.bo _dy from my Sanity GROQ query. But moving this to value is not working. What should go inside the value now? It’s not so clear from the documentation really.


const query = `*[_type == "post" && slug.current == $slug][0] {
        _id,
        _createdAt,
        title,
        description,
        mainImage,
        slug,
        body[]{
          ...,
          asset -> {
          ...,
          "_key": _id
          }
         },
        'comments': *[
            _type == "comment" &&
            post._ref == ^._id &&
            approved == true
          ],
        author -> {
         name,
         image
        }
      }`;
My PortableText the value is wrong.. I am trying to load all the post->body content inside the value. But now it’s giving me errors.


<PortableText
            value={post.body}
            components={{
              types: {
                image: ({ asset, alt, width }) => (
                  <figure className="inline-image">
                    <img
                      src={urlFor(asset).width(width).fit("max").url()}
                      alt={alt}
                    />
                    <figcaption>
                      {caption && <span className="float-left">{caption}</span>}
                      {credit && (
                        <i className="float-right">Photo credit: {credit}</i>
                      )}
                    </figcaption>
                  </figure>
                ),
              },
              block: {
                h1: (props: any) => (
                  <h1 className="text-2xl font-bold my-5" {...props} />
                ),
                h2: (props: any) => (
                  <h2 className="text-xl font-bold my-5" {...props} />
                ),
                h3: (props: any) => (
                  <h3 className="text-lg font-bold my-5" {...props} />
                ),
                h4: (props: any) => (
                  <h4 className="text-md font-bold my-5" {...props} />
                ),
                blockquote: (props: any) => (
                  <blockquote
                    className="text-xl text-red-300 font-bold my-5"
                    {...props}
                  />
                ),
              },

              marks: {
                link: ({ href, children }: any) => (
                  <a href={href} className="text-blue-500 hover:underline">
                    {children}
                  </a>
                ),
              },

              listItem: {
                li: ({ children }: any) => (
                  <li className="ml-4 list-disc">{children}</li>
                ),
              },
            }}
          />

AI Update

Your code looks correct! The value={post.body} prop is indeed the right approach when migrating from react-portable-text (which used content) to the newer @portabletext/react (which uses value).

Based on your GROQ query and component setup, the issue is likely in how you're handling the image type in your custom components. The @portabletext/react library passes props differently than the old library.

Here's what needs to change in your image component:

<PortableText
  value={post.body}
  components={{
    types: {
      image: ({ value }) => {  // Changed from destructuring asset directly
        return (
          <figure className="inline-image">
            <img
              src={urlFor(value.asset).width(value.width || 800).fit("max").url()}
              alt={value.alt || ''}
            />
            <figcaption>
              {value.caption && <span className="float-left">{value.caption}</span>}
              {value.credit && (
                <i className="float-right">Photo credit: {value.credit}</i>
              )}
            </figcaption>
          </figure>
        );
      },
    },
    // ... rest of your components
  }}
/>

The key difference: in @portabletext/react, custom type components receive a value prop that contains all the block's data, whereas the old library passed properties directly. So you need to access value.asset, value.alt, value.caption, etc.

Also note that you were referencing caption and credit variables that weren't defined - you'll need to access them from value.caption and value.credit.

Your GROQ query looks good for fetching the body content. The value={post.body} is correct - it should be the array of blocks returned from your query.

If you're still getting errors, check:

  1. That post.body is actually defined and is an array
  2. The exact error message - it will help identify if there's a specific block type or mark causing issues
  3. That you've installed the correct package: npm install @portabletext/react (not react-portable-text)

The Portable Text introduction guide and beginner's guide have more examples of custom serializers if you need additional reference.

Show original thread
28 replies

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?