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!