Search code examples
arraysjsonnext.js13strapi

How do I pull data from an array within a JSON file (Using Strapi API, and Next JS 13)?


I am building a website using Strapi & Next JS, and I cannot access text within an array. I am trying to dynamically pull data from strapi into a component created, called info_block which includes an image (media), a headline (short text field), and content(richtext). infoblock wireframe

Currently, the rich textfield doesn't render in the browser, and I believe my difficulty relates to the richtext field including an array of objects. Those who have posted on online discussions don't have as many levels in their JSON as me, and I don't know why.

EDIT: Strapi has two different rich text data types. There are Rich text blocks, and rich text markdown types. Rich text(blocks) render paragraphs as objects. If I use Richtext markdown, then I can fetch the strapi data I need without needing to use a callback function to filter through an array.

INFOBLOCK JSON FROM API
 
"info_blocks": {
  "data": [
    {
      "id": 1,
      "attributes": {
        "Headline": "the experience.",
        "ShowImageRight": false,
        "createdAt": "2023-12-16T22:36:41.184Z",
        "updatedAt": "2023-12-22T23:32:56.637Z",
        "publishedAt": "2023-12-16T22:38:43.325Z",
        "content": [
          {
            "type": "paragraph",
            "children": [
              {
                "type": "text",
                "text": "At Sam’s Surfcamp, we invite you to embark on an unforgettable surfing adventure. Nestled in the heart of [Location] our surf camp offers an exhilarating experience for beginners, intermediate surfers, and seasoned wave riders alike."
              }
            ]
          },
        {
          "type": "paragraph",
          "children": [
            {
              "type": "text",
              "text": ""
            }
          ]
        },
      ],
      "Image": {},
      "button": {}
    }
  ]
}
PAGE JSX THAT RENDERS THE INFOBLOCKS.

export default async function Home() {
    const data = await fetchDataFromStrapi("infoblocks-landing?populate[info_blocks][populate]=*");
    const infoBlockData = processInfoBlocks(data);
    // console.log(infoBlockData);
    
    const heroHeadline = (
        <>
            <h1>barrel.</h1>
            <h1>your.</h1>
            <h1>happiness.</h1>
        </>
    );

    return (
        <main>
            <HeroSection headline={heroHeadline} />
            {infoBlockData.map((data) => (
                <InfoBlock key={data.id} data={data} />
            ))}
        </main>
    );
}

The button and the headline render, but the copywriting doesn't

REACT INFOBLOCK COMPONENT

const InfoBlock = ({ data }) => {
    const { Headline, content, showImageRight, imageSrc, button } = data;
    
    return (
        <div className={`info ${showImageRight ? "info--reversed" : ""}`}>
            <img className="info__image" src={imageSrc || "/info-block-1.png"} alt="" />
            <div className="info__text">
                <h2 className="info__headline">{Headline}</h2>
                {content}
                {button}
            </div>
        </div>
    );
};


export default InfoBlock;

Currently, the information that I am trying to call is logged in the console with the console.log(paragraph.children[0].text), but it is undefined within my infoblock component.

STRAPI UTILS FILE

export function processInfoBlocks(data) {
    const infoBlocksRaw = data.attributes.info_blocks.data;

    return infoBlocksRaw.map((infoBlock) => ({
        ...infoBlock.attributes,
        imageSrc: BASE_URL + infoBlock.attributes?.Image?.data?.attributes?.url,
        id: infoBlock.id,
        /* calling the method created below and assigning it to content */
        content: createInfoBlockContent(infoBlock.attributes?.content),
        button: createInfoBlockButton(infoBlock.attributes?.button),
    }));
}

function createInfoBlockContent(contentData) { 
    return (
        contentData.map((paragraph) => {
            <p className="copy">{paragraph.children[0].text}</p>
            {console.log(paragraph.children[0].text)}
        })
    );
}

Inside of Strapi admin, I am using a single page type, just for practicing purposes that includes 3 infoblocks. Does anyone know the following:

  1. Has Strapi Richtext API been updated, composed of objects rather than a string?

  2. How do I access an array of objects in a json file? Since "Headline" can be fetched, I believe this is my difficulty.

The closest I have reached is by mapping through the array, and logging the results to the console. Now I just have to figure out why the component data result is undefined.

Let me know if there is more information I can add to this post. Thank you for reading!


Solution

  • You can't render markdown or json from blocks straight away. For markdown you have to use something like https://www.npmjs.com/package/markdown-to-jsx

    For Blocks, a standard pattern of rendering blocks is:

    content.map(({ type, children }) => {
      switch(type) {
        case 'paragraph': 
           return <MyParagraph {...children}/>;
        defalut: 
          return null;
      }
    }))
    

    I see children is an array, suspect it's for handling bold, italic... So full rendering code striped to components would be something like:

    const BlocksRenderer = ({content}) => content.map(({type, children}) => {
      switch(type) {
        case 'paragraph': 
           return <p><TextSubsetRenderer items={children}/><p/>;
        defalut: 
          return null;
      }
    });
    
    export default BlocksRenderer;
    
    const TextSubsetRenderer = ({items}) => items.map(({type, text}) => {
      switch(type){
        case 'text': 
          return <span>{text}<span>;
        default
          return null;
      }
    })
    
    export default TextSubsetRenderer;
    

    Guess this approach can be a bit tweaked (use functions instead of components etc.)... but the logically seems fine...

    Anyways there is already a plugin to render blocks

    And i would also recommend to use one of this two plugins:

    About your question, not sure why would you need filtering... Or what you want to filter, if you render blocks, you have to map and switch. If you render markdown you need markdown processor...