Search code examples
javascriptreactjsnext.jscontentfulcontentful-api

Problem reaching Contentful's API with getEntry() helper: no content is returned from promise of found entry


Intro

Few days ago I posted a question here asking for help with contentful API for a javascript query of objects and since then I've been struggling to understand one thing about this API use with NextJS.

The deal here is that there are two helpers provided by contentful package: getEntries() and getEntry(). The first helped me indeed to fetch content for a page which I'm working one but I feel that due to the structure of the content model, I'm having issues on reaching out for the linked entries within this page.

The working code

I have contentfulClient set in a separate file but for explanation's sake, let me concatenate both:

import Head from 'next/head'

// ./lib/contentful-client.js
const { createClient } = require('contentful')

const contentfulClient = createClient({
  space: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE,
  accessToken: process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN
})

module.exports = contentfulClient

// ./pages/index.js
export async function getStaticProps() {
  let data = await contenfulClient.getEntries({
    content_type: 'page',
    'field.slug': 'page_slug'   // <-- this is being manually set for each page
  })
  return {
    props: {
      pagina: data.items[0]     // <-- where the props are build and exported
    }
  }
}

export default function HomePage({ pagina }) {

  const { title, slug, sections } = pagina.fields

  return (
    <div>
      <Head>
        <title>{title}</title>
      </Head>
      <main id={slug}>
        {sections.map((section) => {
          console.log(section)
        })}
      </main>
    </div>
  )
}

This will fetch content for each section from which the page is built. The console displays a log of the actual structure up to some level and here is the catch.

The Content Model structure

The actual Content Model for pages was developed to provide flexibility to user, which choice can be both to provide an image attachment for a section or not, or to build another component with other properties.

├── Page
|     ├── title
|     ├── slug
|     └── section
|           ├── title
|           ├── slug
|           ├── content
|           └── components
|                 ├── imageComponent
|                 ├── formComponent
|                 └── extraComponent
.                     .
.                     .
.                     .

With the previous function getStaticProps(), the code that maps sections can reach for that array of components but won't return it's content:

// console output
{sys: {…}, fields: {…}}
  fields:
    componentes: Array(3)
      0:
        sys: {type: "Link", linkType: "Entry", id: "X1n2uZJfMMwtJbY3H7Z6M"}
        __proto__: Object
        1:
        sys: {type: "Link", linkType: "Entry", id: "19gYv80phM75jgmAKCgwAI"}
        __proto__: Object
        2:
        sys: {type: "Link", linkType: "Entry", id: "4xCKnCVG142Mxl9s1iNsPZ"}
        __proto__: Object
        length: 3
        __proto__: Array(0)

So in order to do that, I realized that getEntry(<entry_id>) would be a nice way to reach that content perhaps turn it into a json structure somehow.

// ./pages/index.js
return (
    <div>
      <Head>
        <title>{title}</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main id={slug}>
        {secoes.map((secao) => {
          contentfulClient.getEntry(secao.sys.id)
            .then((entry) => console.log(entry))
            .catch((err) => console.error(err))
        })}

      </main>
      <footer >
        (footer)
      </footer>
    </div>
  )

// console output
{sys: {…}, fields: {…}, toPlainObject: ƒ}
  fields:
    slug: "hero"
    title: "This is my title!"
    components: Array(3)
      0:
        fields:
          main: Array(2)
            0:
              fields:
                file:
                  contentType: "image/png"
                  details: {size: 188420, image: {…}}
                  fileName: "bkgHero.png"
                  url: "<path_to>/bkgHero.png" // <-- can reach the paths for images, for instance. 
                  __proto__: Object
                title: "heroBkg"
                __proto__: Object
              sys:
                createdAt: "2020-06-08T15:18:42.394Z"
                environment: {sys: {…}}
                id: "5E2h7vNFAFTW4BYKWPJo9E"
                locale: "pt-BR"
                revision: 3
                space: {sys: {…}}
                type: "Asset"
                updatedAt: "2020-06-30T19:05:51.537Z"
                __proto__: Object
              __proto__: Object

The "broken" code

Well, it's not really broken. It logs and I can see all the content from these objects but I can't put my finger on it as .then() promise won't return a value that I can seem to use in my page.

// ./pages/index.js
    return (
        <div>
          <Head>
            <title>{title}</title>
            <link rel="icon" href="/favicon.ico" />
          </Head>
          <main id={slug}>
            {secoes.map((secao) => {
              contentfulClient.getEntry(secao.sys.id)
                .then((entry) => <span>{entry.fields.slug}</span>) // <-- Trying to fetch content from that entry
                .catch((err) => console.error(err))
            })}
    
          </main>
          <footer >
            (footer)
          </footer>
        </div>
      )

No error is returned from console and my page structure won't render a span with that slug:

<div id="__next">
  <div>
    <main id="index"></main>
    <footer>(footer)</footer>
  </div>
</div>

The is an answered question here telling that getEntry() is the proper way to reach that content and I've tried really hard with that method with no sucess. Other says to query with getEntries() and tried that too, again, with no success.

I believe the major problem here is that NextJS forces me to fetch these contents in the Index to be passed over by props to the components of my page and theoretically it should work in both ways, cause both ways will return me content in log. I just can't reach it as a prop or in a chain of data.

Not sure how to proceed. Should I rethink this whole thing in another way or even try GraphQL API? Never used that outside of Gatsby and even with that I'm no expert. Would appreciate any thoughts on that matter and suggestions on how to proceed.


Solution

  • Contentful DevRel here. 👋

    Looking at the code and how you approached using getStaticProps looks valid to me and is the way to go, imo.

    To access links more than one level down in nesting you have to define the include query parameter. include defaults to 1 which explains why you can access sections but not the content of components. When dealing with nested content structures it's recommended to increase the number of included nesting levels.

    // ./pages/index.js
    export async function getStaticProps() {
      let data = await contenfulClient.getEntries({
        content_type: 'page',
        'field.slug': 'page_slug'
        include: 5 // <-- this defaults to 1
      })
      return {
        props: {
          pagina: data.items[0]
        }
      }
    }