Unable to render blocks in StructuredText in Remix [Edited title]

Hello,

I am using DatoCMS as an Headless CMS to render Blogs in my Remix based Web App, where I am struggling a little to render the Blocks inside of the <StructuredText /> from the react-datocms, and below is my Schema β†’ Models & Blocks:

and attaching Blocks Schema screenshot in the comments.

I am using this Block β†’ Blog Additional Image β†’ Additional Images inside of the Models β†’ Blog Description.

and below is my code snippets for how I am using the DatoCMS GraphQL Query to fetch the Document.

Below is the function which stores the GraphQL Query copied from CDA Playground:

export const getSingleDatoCMSBlogsGraphQLQuery = (blogSlug: string) => {
  return `query GetAllBlogs {
    blog (filter: { blogSlug: { eq: "${blogSlug}" } }) {
      id
      blogTitle
      blogDisplayPicture {
        responsiveImage {
            alt
            title
            src
            srcSet
            sizes
          }
      }
      blogSlug
      blogTags
      blogCategory
      blogWrittenOn
      blogDescription {
        blocks {
          __typename
          additionalImages {
            responsiveImage {
              alt
              title
              webpSrcSet
              sizes
              aspectRatio
            }
          }
        }
        value
      }
      seoDetails {
        title
        description
        image {
          url
          alt
          title
          format
        }
      }
    }
}`;
};

and below is the function definition to make the fetch() call and the filename is defined as blogs.server.ts to make the File to be in the Server Side when building for the Production in Remix:

const graphQLFetchHeaders = {
  "Content-Type": "application/json",
  Accept: "application/json",
  Authorization: `Bearer ${blogsDatoCmsToken}`,
};

export const fetchSingleDatoCMSBlog = async (blogSlug: string) => {
  try {
    const resp = await fetch("https://graphql.datocms.com/", {
      method: "POST",
      headers: graphQLFetchHeaders,
      body: JSON.stringify({ query: getSingleDatoCMSBlogsGraphQLQuery(blogSlug) }),
    });

    const data = await resp.json();

    return { datoCMSBlog: data.data.blog };
  } catch (error) {
    console.log("πŸš€ ------------------------------------------πŸš€");
    console.log("πŸš€ ~ fetchSingleDatoCMSBlog ~ error:", error);
    console.log("πŸš€ ------------------------------------------πŸš€");
    return {
      datoCMSBlog: null,
    };
  }
};

and below is the /routes/blog.$blogSlug.tsx in the Remix:

export const loader: LoaderFunction = async ({ params }) => {
  try {
    const blogSlug = params.blogSlug;

    const { datoCMSBlog } = await fetchSingleDatoCMSBlog(blogSlug as string);

    if (datoCMSBlog.id === "" && datoCMSBlog.constructor === Object) {
      return redirect("/404");
    }
    return { datoCMSBlog };
  } catch (error) {
    console.log("πŸš€ --------------------------πŸš€");
    console.log("πŸš€ ~ loader ~ error:", error);
    console.log("πŸš€ --------------------------πŸš€");
    return { datoCMSBlog: null };
  }
};

now I am using the useLoaderData() inside the component which is wrapped & rendered inside of blog.$blogSlug.tsx file, and I am trying to use the StructuredText from the react-datocms as below:

<StructuredText
            data={datoCMSBlog.blogDescription}
            renderBlock={({ record }: { record: any }) => {
              switch (record.__typename) {
                case "BlogAdditionalImageRecord":
                  return <Image data={record?.image.responsiveImage} />;
                default:
                  return null;
              }
            }}
          />

for above code snippet I am getting below error:

Error: The Structured Text document contains a 'block' node, but cannot find a record with ID Izu6nPujRDaYTU7ox-WrwA inside data.blocks!
    at new RenderError (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\datocms-structured-text-utils\dist\cjs\render.js:29:28)
    at file:///C:/Users/dhaval/Desktop/Coursera%20Learning/DV_Resume_Nextjs/dv-resume/node_modules/react-datocms/dist/esm/StructuredText/index.js:72:27
    at Object.apply (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\datocms-structured-text-utils\dist\cjs\render.js:40:16)
    at transformNode (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\datocms-structured-text-utils\dist\cjs\render.js:56:34)
    at C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\datocms-structured-text-utils\dist\cjs\render.js:48:20
    at Array.map (<anonymous>)
    at transformNode (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\datocms-structured-text-utils\dist\cjs\render.js:47:14)
    at Object.render (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\datocms-structured-text-utils\dist\cjs\render.js:76:18)
    at render (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\datocms-structured-text-generic-html-renderer\dist\cjs\index.js:93:44)
    at StructuredText (file:///C:/Users/dhaval/Desktop/Coursera%20Learning/DV_Resume_Nextjs/dv-resume/node_modules/react-datocms/dist/esm/StructuredText/index.js:19:20)
    at renderWithHooks (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:5724:16)
    at renderIndeterminateComponent (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:5798:15)
    at renderElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6023:7)
    at renderNodeDestructiveImpl (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6181:11)
    at renderNodeDestructive (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
    at renderNode (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6336:12)
    at renderHostElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:5708:3)
    at renderElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6029:5)
    at renderNodeDestructiveImpl (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6181:11)
    at renderNodeDestructive (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
    at renderNode (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6336:12)
    at renderHostElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:5708:3)
    at renderElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6029:5)
    at renderNodeDestructiveImpl (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6181:11)
    at renderNodeDestructive (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
    at renderNode (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6336:12)
    at renderChildrenArray (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6288:7)
    at renderNodeDestructiveImpl (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6218:7)
    at renderNodeDestructive (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
    at renderNode (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6336:12)
    at renderHostElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:5708:3)
    at renderElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6029:5)
    at renderNodeDestructiveImpl (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6181:11)
    at renderNodeDestructive (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
    at renderIndeterminateComponent (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:5852:7)
    at renderElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6023:7)
    at renderNodeDestructiveImpl (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6181:11)
    at renderNodeDestructive (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
    at renderNode (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6336:12)
    at renderHostElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:5708:3)
    at renderElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6029:5)
    at renderNodeDestructiveImpl (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6181:11)
    at renderNodeDestructive (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
    at renderNode (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6336:12)
    at renderChildrenArray (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6288:7)
    at renderNodeDestructiveImpl (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6218:7)
    at renderNodeDestructive (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
    at renderNode (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6336:12)
    at renderHostElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:5708:3)
    at renderElement (C:\Users\dhaval\Desktop\Coursera Learning\DV_Resume_Nextjs\dv-resume\node_modules\react-dom\cjs\react-dom-server.node.development.js:6029:5)

any help on this how to fix this issue and render properly?

Block Schema

Hi @dhavalveera1, welcome to the Dato community!

In your GraphQL, I think maybe you need to add an id inside additionalImages {}?

You can also take a look at our Remix blog example (or source code) to see how we handle image rendering inside structured text.

Does that help at all?

Like this, hopefully?

blogDescription {
        blocks {
          __typename
          additionalImages {
            id # <-- This was missing from your original query
            responsiveImage {
              alt
              title
              webpSrcSet
              sizes
              aspectRatio
            }
          }
        }
        value
      }

Thank You!.. This resolved the issue!.. As well as I had to change the Blocks from Asset Gallery to Single Asset…

@roger if I use Asset Gallery then how the rendering will work? because the Asset Gallery will be an Array, can you please guide me on this?

In that we case we just return an array of images, as you noticed. How you want to render them on your frontend is up to you, though. In our ecommerce demo (built in Next, not Remix unfortunately), we show a simple page with different images, for example: https://ecommerce-website-demo-blond.vercel.app/en/product/high-waist-pencil-skirt. That corresponds to the the source code at ecommerce-website-demo/components/Products/ProductView.tsx at main Β· datocms/ecommerce-website-demo Β· GitHub

I don’t have a lot of Remix experience, but I think you should still be able to use standard React components, right, as long as you pass it the right data? If so, you should be able to use any React UI kit to build a gallery, like Image List React component - Material UI or react-multi-carousel - npm or react-image-gallery - npm (just random examples).

Basically, all we give you are a list of URLs (and other data you request) in an array. Your frontend has to take those and render them (as in images.map(imgData => <YourComponent img={imgData}/>) however you like.

Sorry that we don’t have a ready-built example for that in Remix, but hopefully some of the above helps? Please let us know if you have a more specific vision in mind for how you want to show them and need help doing so.

Hello @roger ,

so in the below code, inside of return statement I can run a loop to display all the Images from Asset Gallery like:

renderBlock={({ record }: { record: any }) => {
              switch (record.__typename) {
                case "BlogAdditionalImageRecord":
                  return record.image.additionalImages.map((img) =>  <Image data={img} />);
                default:
                  return null;
              }
            }}

something like this?

Yes, exactly. That will get you a basic list of images, one after another, like this:

The relevant query part looks like this:

content {
  value
  blocks {
    __typename
    ...on ImageBlockRecord {
      id
      image {
        responsiveImage(imgixParams: {fm: jpg, fit: crop, w: 2000, h: 1000 }) {
          ...responsiveImageFragment
        }
      }
    }
    ... on ImageGalleryRecord { # <-- This is the asset gallery block
      id
    assetGallery { # <-- This is the asset gallery field
      responsiveImage {
        ...responsiveImageFragment # <-- Reusing the same fragment
      }
    }
  }
}
}

And the slightly modified renderBlock code:

renderBlock={({record}) => {
    switch (record.__typename) {

        // Single image block
        case 'ImageBlockRecord': {
            return (
                <Image
                    className="grid__image"
                    data={record.image.responsiveImage}
                />
            );
        }

        // Asset gallery block
        case 'ImageGalleryRecord': {
            return record.assetGallery.map(image =><Image
                className="grid__image"
                data={image.responsiveImage}
            /> )
        }
    }

    // Unhandled blocks
    return (
        <>
            <p>Don't know how to render a block!</p>
            <pre>{JSON.stringify(record, null, 2)}</pre>
        </>
    );
}}

Hello @roger ,

content {
  value
  blocks {
    __typename
    ...on ImageBlockRecord {
      id
      image {
        responsiveImage(imgixParams: {fm: jpg, fit: crop, w: 2000, h: 1000 }) {
          ...responsiveImageFragment
        }
      }
    }
    ... on ImageGalleryRecord { # <-- This is the asset gallery block
      id
    assetGallery { # <-- This is the asset gallery field
      responsiveImage {
        ...responsiveImageFragment # <-- Reusing the same fragment
      }
    }
  }
}
}

as you shared this GraphQL Query Snippet, how does it’ll identify the Block Type? I mean like it’s Single Asset Image or Asset Gallery?

Furthermore, the GraphQL Query you shared seems little bit different then what I make from the CDA Playground, As I don’t have much experience with it, I just created GraphQL Queries from the CDA Playground only.

ImageBlock and ImageGallery are just the names of the blocks. The identification happens in the switch (record.__typename) in Javascript, where it differentiates by the block name and then renders a single image for ImageBlockRecord and an array for ImageGalleryRecord.

I think in this example, ImageGallery would be like your BlogAdditionalImageRecord (the block name). And assetGallery would be the asset gallery field within that block, similar to your additionalImages, I think? But my example also had a separate single-image block to show you how to use the two together (via the switch statement).

If you want to send me your actual project with some example gallery and single-image fields, I can show you what the query would look like in your actual project? (You can either DM me here or email support@datocms.com and mention this thread)

Following up over email now. Let’s move the convo there so we can cover your specific project details :slight_smile: