Unlock seamless workflows and faster delivery with our latest releases – get the details

Migrating to @portabletext/react and encountering issues with the "value" property.

28 replies
Last updated: Feb 20, 2022
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>
                ),
              },
            }}
          />

Feb 19, 2022, 12:04 AM
user M
Take a look at the “value” I am passing the GROQ’s post-&gt;body to it
Feb 19, 2022, 12:13 AM
What error are you getting?
Feb 19, 2022, 12:15 AM
This
Feb 19, 2022, 12:16 AM
When I remove the types =&gt; image it works.
Feb 19, 2022, 12:16 AM
Ah, the issue is with your asset being undefined, not with value not being passed in then. I don't think the way you're querying images inside of block content correctly.
Feb 19, 2022, 12:19 AM
But I have an image in the content editor but it returns undefined.
Feb 19, 2022, 12:22 AM
The way you currently have it set you're adding a field called
asset
to each block. When a block doesn't have an asset to expand, it sets it to
null
. You're passing in a
null
value into your serializer that is now running on each block.
Feb 19, 2022, 12:26 AM
user A

Now it’s saying:
TypeError: Cannot read properties of undefined (reading ‘asset’)
Feb 19, 2022, 12:33 AM
import Header from "@components/Header";
import { sanityClient, urlFor } from "sanity";
import { Post } from "typings";
import { GetStaticProps } from "next";
// import PortableText from "react-portable-text";

import { PortableText } from "@portabletext/react";

interface Props {
  post: Post;
}

function Post({ post }: Props) {
  console.log(post);
  return (
    <main>
      <Header />

      <img
        className="w-full h-40 object-cover"
        src={urlFor(post.mainImage).url()!}
      />

      <article className="max-w-3xl mx-auto p-5">
        <h1 className="text-3xl mt-10 mb-3">{post.title}</h1>
        <h2 className="text-xl font-light text-gray-500 mb-2">
          {post.description}
        </h2>

        <div className="flex items-center space-x-2">
          <img
            className="h-10 w-10 rounded-full object-cover"
            src={urlFor(post.author.image).url()}
          />

          <p className="font-extralight text-sm">
            Blog post by{" "}
            <span className="text-green-600">{post.author.name}</span> -
            Published at{" "}
            {new Date(post._createdAt).toLocaleDateString("nl-NL", {
              year: "numeric",
              month: "2-digit",
              day: "2-digit",
            })}
          </p>
        </div>

        <div className="mt-10">
          <PortableText
            value={post.body}
            components={{
              types: {
                image: ({ node: { asset, alt, width } }: any) => (
                  <figure className="inline-image">
                    <img
                      alt={alt}
                      src={urlFor(asset).width(1280).fit("max").url()}
                    />
                  </figure>
                ),
              },
              block: {
                h1: ({ children }) => (
                  <h1 className="text-2xl font-bold my-5">{children}</h1>
                ),
                h2: ({ children }) => (
                  <h2 className="text-xl font-bold my-5">{children}</h2>
                ),
                h3: ({ children }) => (
                  <h3 className="text-lg font-bold my-5">{children}</h3>
                ),
                h4: ({ children }) => (
                  <h4 className="text-md font-bold my-5">{children}</h4>
                ),
                // 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>
                ),
              },
            }}
          />
        </div>

        <hr className="my-5 border border-yellow-500 mb-10" />
        <h3 className="text-sm text-yellow-500">Enjoyed this article?</h3>
        <h4 className="text-3xl font-bold">Leave a comment below!</h4>
        <hr className="py-3 mt-2" />

        <form className="flex flex-col py-5 mx-auto mb-10">
          <label className="block mb-5">
            <span className="text-gray-700">Name</span>
            <input
              className="shadow border rounded py-2 px-3 form-input mt-1 block w-full ring-yellow-500 outline-none focus:ring"
              placeholder="John Appleseed"
              type="text"
            />
          </label>
          <label className="block mb-5">
            <span className="text-gray-700">Email</span>
            <input
              className="shadow border rounded py-2 px-3 form-input mt-1 block w-full ring-yellow-500 outline-none focus:ring"
              placeholder="John Appleseed"
              type="text"
            />
          </label>
          <label className="block mb-5">
            <span className="text-gray-700">Comment</span>
            <textarea
              className="shadow border rounded py-2 px-3 form-textarea mt-1 block w-full ring-yellow-500 outline-none focus:ring"
              placeholder="John Appleseed"
              rows={8}
            />
          </label>
        </form>
      </article>
    </main>
  );
}

export default Post;

export const getStaticPaths = async () => {
  const query = `*[_type == "post"] {
        title,
        slug {
        current
      }
      }`;

  const posts = await sanityClient.fetch(query);

  const paths = posts.map((post: Post) => ({
    params: {
      slug: post.slug.current,
    },
  }));

  return {
    paths,
    fallback: "blocking",
  };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const query = `*[_type == "post" && slug.current == $slug][0] {
        _id,
        _createdAt,
        title,
        description,
        mainImage,
        slug,
        body[]{
          ...,
          asset -> {
          alt,
          width,
          ...,
          "_key": _id
          }
         },
        'comments': *[
            _type == "comment" &&
            post._ref == ^._id &&
            approved == true
          ],
        author -> {
         name,
         image
        }
      }`;

  const post = await sanityClient.fetch(query, {
    slug: params?.slug,
  });

  if (!post) {
    return {
      notFound: true,
    };
  }

  return {
    props: {
      post,
    },
    revalidate: 60, // After 60 seconds update old cached version.
  };
};

Feb 19, 2022, 12:41 AM
What does your query in getStaticProps return when you run it in Vision and pass the slug that’s causing this error?
Feb 19, 2022, 1:00 AM
Give me one sec
Feb 19, 2022, 1:01 AM
When passing only “body” to the. GROQ in the vision. I get all body items including the type image and the asset.
Feb 19, 2022, 1:08 AM
But on the front-end it wont show
Feb 19, 2022, 1:08 AM
Can you try removing the projection under
body[]
? As Racheal pointed out, asking for
asset
explicitly will look for it on every block. If you must drill down to do your
_key
thing, you’ll want to do so conditionally.

// ...
description,
mainImage,
slug,
body[],
'comments': *[
// ...
Feb 19, 2022, 1:15 AM
Can you try removing the projection under
body[]
? As Racheal pointed out, asking for it explicitly will cause it to look for it on everything.

// ...
description,
mainImage,
slug,
body[],
'comments': *[
// ...
Feb 19, 2022, 1:15 AM
Now I have this:
const query = `*[_type == "post" && slug.current == $slug][0] {
  _id,
  _createdAt,
  title,
  description,
  mainImage,
  slug,
  body[],
  'comments': *[
      _type == "comment" &&
      post._ref == ^._id &&
      approved == true
    ],
  author -> {
    name,
    image
  }
}`;

types: {
    image: ({ node: { asset, alt, width } }) => (
        <figure className="inline-image">
        <img src={urlFor(asset).width(1280).fit("max").url()} />
        </figure>
    ),
},
},

TypeError: Cannot read properties of undefined (reading 'asset')
Feb 19, 2022, 1:18 AM
The “node” does nothing when i console log post.body I get an array of types including the image type where the _ref is defined. But I keep getting undefined asset. Asset is not recognized while Sanity vision shows everything correctly.
Feb 19, 2022, 1:34 AM
The H1,H2,H3,H4, LI, OL everything works else
Feb 19, 2022, 1:36 AM
The H1,H2,H3,H4, LI, OL everything works else
Feb 19, 2022, 1:36 AM
Isn’t “node” used for GraphQL?
Feb 19, 2022, 1:40 AM
That suggestion was before knowing you had switched to the new portable text renderer. I would try switching it back to what you had before.
Although
node
is found in GraphQL, it's also a prop from some block content types.
Feb 19, 2022, 2:13 AM
user A
I have switched to the new new portable text like you have suggested. Why is the image not working? Changed the query and still no luck
Feb 19, 2022, 2:15 AM
user A
That was it!!! 😄 Could you please, explain me where does the “value” comes from the plugin? Because I don’t see it in the GROQ query.
Feb 19, 2022, 11:47 AM
Thanks
user M
and
user A
Feb 19, 2022, 11:48 AM
Glad to hear it, Nino.
value
comes from the renderer rather than being a property on your content, so you won’t see it in a GROQ query.
Feb 19, 2022, 3:48 PM
Great to see you and Geoff worked it out!!
Feb 20, 2022, 4:13 AM

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?