Seth
Seth

Reputation: 1072

Can I create a React Element from an HTML string or dom node/element

I'm retrieving an HTML string from SharePoint and need to parse and modify the data and build a react element to be displayed in my react application.

Basically, I have code (as string) returning to me in a format similar to:

"
<div>
    <div data-sp-canvasdataversion="1">This could be a header</div>
    <div data-sp-canvasdataversion="1"><img src="titleimage.jpg"></div>
    <div data-sp-canvasdataversion="1"><a href="pdfLink.pdf">This is a link to a PDF</a></div>
</div>
"

and from that I need to cycle through the children and build a new React element containing some parts of what was returned and some new parts such as

<div>
    <div data-sp-canvasdataversion="1">This could be a header</div>
    <div data-sp-canvasdataversion="1"><img src="titleimage.jpg"></div>
    <PDFViewer file={""pdfLink.pdf""}></PDFViewer> 
</div>

I was originally using dangerouslySetInnerHTML which worked to simply display the data, but now i need to remove a chunk of the html, create a react element based on the data and inject the new element back into the code. Since i'm trying to insert a component now, vanilla html won't work.

I'm able to cycle through the children by converting it to a dom node but I can't figure out how to use a dom node or element as a React Element Child.

I've tried:

let element = React.createElement('div', {}, HTMLString); 
let node = document.createRange().createContextualFragment(HTMLString).firstChild;

let element = React.createElement('div', {}, node); 
let node = document.createRange().createContextualFragment(HTMLString).firstChild;
let node2 = document.createElement("div");
node2.appendChild(node);
node2 = node2.firstElementChild as HTMLDivElement;

let element = React.createElement('div', {}, node2); 

None work as needed and give me the error Objects are not valid as a React child (found: [object HTMLDivElement]). or similar.

What i need is something like:

let element = React.createElement('div'); 
let node = document.createRange().createContextualFragment(HTMLString).firstChild;

node.childNodes.forEach(child => {
    if(...//child IS NOT pdf)
        element.appendChild(child)
    else if(...//child IS pdf){
        ...
        element.appendChild(<PDFViewer file="linktopdf.pdf">)
    }
})

Then I would expect to be able to use that in render

render {
    ...
    return(
        <div className="container">
            {element}
        </div>
    );
}

Please let me know if this is even possible and how. The only possible solution I could think of was maybe saving the child elements as strings and using dangerouslySetInnerHTML to generate all of them but I really want to get away from using dangerouslySetInnerHTML, especially like that.

Upvotes: 4

Views: 15143

Answers (2)

colin
colin

Reputation: 324

For below React v17.0: try this react-html-parser

For React v17.0 and above: try this fork (thanks to @thesdev pointing that out)

Upvotes: 4

Seth
Seth

Reputation: 1072

I ended up doing what I didn't want to which is using dangerouslySetInnerHTML. If anyone comes up with a better solution, please feel free to share.

async getArticleComponents(data: any) {
    if (!data)
        return null;

    let slides = []
    let node = document.createRange().createContextualFragment(data).firstChild;
    let children = node.childNodes;

    for (let i = 0; i < children.length; i++) {
        var element = (children[i] as HTMLDivElement);
        var controlElement = element.attributes.getNamedItem("data-sp-controldata");
        var jObj = controlElement ? JSON.parse(controlElement.value) : null;

        if (jObj.controlType === 3) {
            var childEle = element.firstElementChild;
            var webPartData = childEle.attributes.getNamedItem("data-sp-webpartdata");
            var wpJObj = webPartData ? JSON.parse(webPartData.value) : null;

            if (wpJObj.title == "File viewer") {
                let ext = (/.*\.(.*)/i).exec(wpJObj.properties.file)[1].toLowerCase();
                if (ext === "pdf") {
                    var pdf = await fetch(`/api/SharePoint/GetFile?url=${encodeURIComponent(`${wpJObj.serverProcessedContent.links.serverRelativeUrl}`)}`)
                    .then(res => res.text())
                    .then(pdf => pdf ? `data:application/pdf;base64,${pdf}` : null);

                    slides.push(<PDFViewer key={jObj.id} file={{ pdf, fileName: "test" } as IPdfMeta} />);
                }
            } else
                slides.push(<div key={jObj.id} dangerouslySetInnerHTML={{ __html: element.outerHTML }}></div>);
        }
        else if (jObj.controlType !== 0) {
            slides.push(<div key={jObj.id} dangerouslySetInnerHTML={{ __html: element.outerHTML }}></div>);
        }
    }
    return slides;
}

Upvotes: 3

Related Questions