2

I'm using contentful along with react. To render the content of an article i use the documentToReactComponent from contentful.

when you pass this function an array of content coming from contentful along with some options, it goes through it and renders the data in you html. my problem: for some reason I need to get the images of the post using client.getAsset(id). Because contentful sends the ids of each images instead of sending the url... this gives me an error because i can resolve the promise returning the data.

here is the code(simpler version):

const RICHTEXT_OPTIONS = {
  renderNode: {
    [BLOCKS.PARAGRAPH]: (node: Block | Inline, children: ReactNode) => {
      return <p>{children}</p>;
    },
    [INLINES.HYPERLINK]: (node: Block | Inline, children: ReactNode) => {
      return <a href={node?.data.uri}>{children}</a>;
    },
    // issue with the promise below
    [BLOCKS.EMBEDDED_ASSET]: (node: Block | Inline) => {
      const id = node?.data.target.sys.id;
      async function getUrls(id: string) {
        const Img = await client
          .getAsset(id)
          .then((asset) => {
            const imageURL = asset.fields.file.url;
            const imageTitle = asset.fields.title || asset.fields.file.fileName;
            return { imageURL, imageTitle };
          })
          .catch(console.error);
        return Img;
      }

      const images = getUrls(id);

      return (
         <Image
           width={400}
           height={400}
           src={`https://${images.imageURL}`}
           alt="random desc"
         />
      );
    },
  },
};

function ClickedArticle({ article }: { article: PostWithCoverImg }){
 const { title, date, author, Img, content } = article;
 return (
        <div className={styles.clickedArticle__article__content}>
         {documentToReactComponents(content as Document, RICHTEXT_OPTIONS)}
        </div>
 )
}

When i try to async await the EMBEDDED_ASSET block, it return an error: Objects are not valid as a React child. Because the promise is not resolved correctly. How can i solve this?(i am using functional components).

Redhewlett
  • 103
  • 8

3 Answers3

1

The node object does contain the asset fields.

// …
[BLOCKS.EMBEDDED_ASSET]: (node) => {
  console.log(node);

  // Logs:
  //{
  //  nodeType: 'embedded-asset-block',
  //  data: { target: { metadata: [Object], sys: [Object], fields: [Object] } },
  //  content: []
  //}

  return;
},

As a result, you don't have to worry about promises in the renderer. However, you'll have a problem with typescript because @contentful/rich-text-types does not include the metadata and fields fields into its types.

[BLOCKS.EMBEDDED_ASSET]: (node) => {
      return (
         <Image
           width={400}
           height={400}
           src={node.data.target.fields.file.url}
           alt="random desc"
         />
      );
    },

Your issue with the Promise was that you didn't await the result of getUrls(id). Adding await in the renderer would make it async and I'm not sure it would work.

thchp
  • 2,013
  • 1
  • 18
  • 33
  • thks, however my contentful doesn't return the url here, it returns it in the includes object then i have to look through this object and create a map to get them later using their id. this is pretty annoying to do contentful returns so many things you dont need, (in my case we dont use graphQl we only use typescript so my collegue will have to make a giant type and return this include object) – Redhewlett Feb 14 '23 at 17:13
  • Do you have the latest version of your contentful dependencies? It's strange that it behaves differently – thchp Feb 14 '23 at 17:22
  • Did you try to fix your code by adding `await` before `getUrls(id)` and making the embedded asset handler async? I'm not sure it will work but it may be a good compromise – thchp Feb 14 '23 at 17:24
  • i tried but it's not working, basically contentful is really annyoing because of that... so we had to get the asset and use them in the api to fetch the actual url. at least with that the front end stays clean – Redhewlett Feb 19 '23 at 10:23
1

Hello I had the same question and after to read differents sources I made this function that works for me:

export const getImage = async (Id) => {
    try {
        const res = await client.getAssets({"sys.id": Id})
        return 'https:'+res.items[0].fields.file.url
    }catch(err){
        console.log(err)
    }
}
0

So it turns out, when you are using contentful, if you fetch data using fetch functions, it will return the asset's id instead of the id. If you want the whole asset you should use the contentful api to fetch your data. (you get it by installing it in you project).

all you have to do is install contentful then use the functions provided, such as contenful.createClient() then using this client client.getEntry()

I think this is the best way of using contentful. You only fetch data once, and you don't have to query contentful to attach your assets. which will reduce the number of request drasticly (imagine you have a blog post with 30 images... fetching thoses images urls separatly when you already fetched the article seems odd.

Redhewlett
  • 103
  • 8