Rendering images inside blocks from a GraphQL Query (edited title)

Hello Support,

I’ve been using Dato CMS with GraphQL for the last few weeks and have managed to fetch the data no problem.

I’m now starting to use blocks inside Dato which adds a bit more complexity to the query and I’m unable to access the data.

You can see in the attachment how I was accessing data previously for the and tags.

What do I need to do now to pass the data to the HTML elements?

Would Apollo help me here?

Any help would be greatly appreciated!

Thanks,

Richard Gill

Hi @albioncircus,

Hope you’re liking the CMS so far! If I remember correctly, you’re not using React, so we have to manually make a DOM tree out of this data, right? What is your <Image/> component in this case? Is it a third-party lib, or?

Let’s see here… disregarding that for the moment, we can at least look at the other parts, in order:

Fetching the image data from the sections array

For this, using the GraphQL explorer (the ā€œCDA Playgroundā€ tab in your project) is very helpful:

In your example <Image/> component, you’re using homePageData.heroBannerImage.url for the src. But that doesn’t exist, because homePageData = json.data.page, and there’s no heroBannerImage directly under it.

Instead, you have to map over the sections[] array. In React/JSX, you’d do something like sections.map() to get back an array of sections that you’d make into React elements. And you would add HeroBannerRecord._modelApiKey to the GraphQL query so that you can switch on its type and render things accordingly.

But you’re not using React, so for an example, we’ll just concat a string and display it using innerHtml instead. Example (or view on Codesandbox):

const json = {
  data: {
    page: {
      title: "My page title",
      sections: [
        {
          id: "fakeID1",
          _modelApiKey: "hero_banner",
          heroBannerImage: {
            id: "fakeID1.1",
            url: "https://www.datocms.com/images/brand-assets/main-lockup.svg",
          },
        },
        {
          id: "fakeID2",
          _modelApiKey: "single_project",
          title: "Fake Title",
        },
      ],
    },
  },
};

const homePageData = json.data.page;

const sections = homePageData.sections;

document.getElementById("app").innerHTML = `
<h1>${homePageData.title}</h1>
${
  sections
    .map((section) => {
      switch (section._modelApiKey) {
        case "single_project":
          return `<div>This is a project block: ${section.title}</div>`;
          break;

        case "hero_banner":
          return `<img src="${section.heroBannerImage.url}"/>`;
          break;
      }
    })
    .join("") // Because we're not using React, we're just concating a string and don't want the comma
}
`;

That will get you something like:
image

It’s not clear to me what framework/language you’re using if not React, so how you’d make HTML elements out of an array depends on that particular framework. But the general principle is the same… map through each section, figure out what kind of block it is by its _modelApiKey, and then render a different HTML element / component based on that.

I hope that helps? Please let me know if I can clarify anything.


Side note: Using Imgix for better performance

Not related to your question directly, but I should point out that assets you upload to DatoCMS have a URL (like heroBannerImage.url), yes, but they will also have another object inside them: heroBannerImage.responsiveImage). The responsiveImage is data that our image CDN, Imgix, provides. This is really useful for making responsive images, srcsets, thumbnails, blurhashes, etc. Normally, if you’re using React, our react-datocms/image component takes care of all of this for you: Managing images — DatoCMS and react-datocms/docs/image.md at master Ā· datocms/react-datocms Ā· GitHub

If you’re using another framework or vanilla JS, you might want to investigate the Imgix tutorials to see how to best do it: Tutorials | imgix Documentation

This is optional, but will help improve performance and responsiveness for your website viewers.

And how exactly are you making your frontend, anyway? Are you using a framework, handcoding the HTML and JS, or…? If you can provide some details and/or sample code, we can provide more tailored answers in the future, instead of just general tips :slight_smile:

Hello Roger,

A belated Happy new year and thanks for your help!

I’m using the Astro framework for building the front end.

Thanks,

Richard

Hello again Roger,

Once again fantastic support, much appreciated.

I followed your instructions and have it partially working, I just have one more question regarding accessing the nested data.

As you can see from the attachments I can map through the array and and access data one level deep but no more, (returns ā€œundefinedā€).

I think I’m just missing another line of code to filter/map again? (Depending if it’s in another array or an object? The ā€œheroBannerImageā€ is an object but ā€œbuttonsā€ is an array for example)

If you had any suggestions it would be great!

Thanks again,

Richard

@albioncircus, happy new year to you too!

This is where the switch(section._modelApiKey) is needed. Updated example in Astro here.

When you have an array of blocks like this, each block has a different shape depending on what fields it holds. (Generating the TypeScript types from your query can help make this clearer, instead of using any[]).

But basically you have an array of block objects, like this:

Each block has different properties (fields), so you need to distinguish between them accordingly. See the full working example in my link above, but the code in question is something like this:

{sections.map((section) => {
			switch(section._modelApiKey) {
				
				case 'hero_banner':
					return <div>
					<h2>{section.heroHeading}</h2>
					{section.heroBannerImage?.url && <img src={section.heroBannerImage.url} alt={section.heroBannerImage.alt}/>}
					</div>

				case 'fifty_fifty_text_image':
					return <div>
					<p>{section.paragraph1}</p>
					{section.buttons.map(button => <button onclick=`window.location.href='${button.url}'`>{button.label}</button>)}
					</div>
			}
		})}

You have to switch() between them based on their _modelApiKey and treat each block type differently, because they don’t all have the same fields.

I THINK what is going on in your example is that it’s able to render the first block OK, since it’s a HeroBannerRecord and has both. But when the same code hits your second block, your FiftyFiftyTextImageRecord doesn’t have a herobannerImage so it errors out. You can validate this if you use a debugger and step through the sections.map() but that’s my guess as to what might be going on?

It works! Thanks so much Roger!
This is the best tech support service I’ve had in 10 years of being in the industry

1 Like

Awesome! Glad that worked, and thanks for the kind words :slight_smile: Have a great weekend!