Recursively render list of items based on the API response

Viewed 82

I have a list of items that needs to be recursively rendered. I am able to render it upto first level. Can someone help me with the nested recursion. I have posted the relevant functions in a function component. Note that details has been stored in a state variable details

{
    "details": [{
        "title": "Part-1",
        "content": [{
            "type": "text",
            "content": "TextABC"
        }]
    }, {
        "title": "Part-2",
        "content": [{
            "type": "text",
            "content": "TextXYZ"
        }, {
            "type": "list",
            "content": [{
                "text": "TextLMN",
                "type": "text"
            }, {
                "type": "list",
                "content": [{
                    "text": "TextPQR",
                    "type": "text"
                }, {
                    "text": "TextDEF",
                    "type": "text"
                }]
            }]
        }]
    }, {
        "title": "Part-3",
        "content": [{
            "type": "list",
            "content": ["<a target='_blank' href='https://www.example.com'>ABC<\/a>", "<a target='_blank' href='https://www.example1.com'>XYZ<\/a>"]
        }]
    }]
}

Each Item is referenced either with a type that can be text or a list. I have tried the following, but the items with nested list is not working

   const isAnchor = str => {
      return /^\<a.*\>.*\<\/a\>/i.test(str);
   };
  
   const getContentJsx = (value) => {
    return isAnchor(value) ? (
      <li dangerouslySetInnerHTML={{ __html: sanitize(value) }} />
    ) : (
      <li>{value}</li>
    );
  };

  const getDetailJsx = () => {
    return details.map(({ title, content }, index) => {
      return (
        <div key={`${title}${index}`}>
          <h6>
            <span>{title}</span>
          </h6>
          {content?.map(({ type: mainType, content: data }) => (
            <div>
              {mainType === "text" && <p>{data}</p>}
              {mainType === "list" && <ul>{data?.map((contentValue) => getContentJsx(contentValue))}</ul>}
            </div>
          ))}
        </div>
      );
    });
  };

  return (
    <div>
        <>
          {getDetailJsx()}
        </>
      )}
    </div>
  );
1 Answers

As suggested, you can create a recursive component that wraps the logic and makes it possible to call recursively. First, I had to correct your detail object. It has inconsistency within its attributes. Note, for example, text -> content:

"type": "text",
"text": "TextPQR"

to

"type": "text",
"content": "TextPQR"

Moreover, observe that it was separated the part that involves the title from the recursion to facilitate its comprehension and make code cleaner.

const getContentJsx = (value) => {
    return isAnchor(value) ? (
      <li dangerouslySetInnerHTML={{ __html: sanitize(value) }} />
    ) : (
      <li>{recursive(value)}</li>
    );
};

const getDetailJsx = () => {
 return details.map(({ title, content }, index) => {
      return (
        <div key={`${title}${index}`}>
          <h3>
            <span>{title}</span>
          </h3>
        {recursive(content)}
        </div>
      )
    })
  }

const recursive = (content) => {
    return (
      <div>
        {
          content.map(({type: mainType, content: data}, index) => (
              <div key={index}>
                {mainType === "text" && <p>{data}</p>}
                {mainType === "list" && <ul>{getContentJsx(data)}</ul>
               }
              </div>
          ))
        }
      </div>
    )
   

Also as shown here

Related