There is problem with render gallery component: I get string with html from server
let serverResponse = `
<h3>Some title</h3>
<p>Some text</p>
<p>
<img src="">
<img src="">
<img src="">
<br>
</p>
...
`
Now I render this response with dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: serverResponse }} />
But when I got 2 or more repeating <img> tags I want to replace them with component.
How can I do that? I tried to do it with Regex and replace them with <Gallery/> but it doesn't work. I think that I need split string in array of tags and then replace images with <Gallery/> component.
I tried do it with renderToString
...
getGallery = images => {
// Loop throw images and get sources
let sources = [];
if (images) {
images.map(img => {
let separatedImages = img.match(/<img (.*?)>/g);
separatedImages.map(item => sources.push(...item.match(/(https?:\/\/.*\.(?:png|jpg))/)));
});
}
if (sources.length) {
return <Gallery items={sources}>
}
return <div/>
};
...
<div dangerouslySetInnerHTML={{__html: serverResponse.replace(/(<img (.*?)>){2,}/g,
renderToString(this.getGallery(serverResponse.match(/(<img (.*?)>){2,}/g))))}}/>}
And this doesn't work, because I get just html without logic :(
Fist of all, dangerouslySetInnerHTML is not the way to go, you can't insert gallery into in it and have it processed by React. What you need to do is multistep procedure.
1. Parse HTML into document. In this stage you will convert string to valid DOM document. This is very easy to do with DOMParser:
function getDOM (html) {
const parser = new DOMParser()
const doc = parser.parseFromString(`<div class="container">${html}</div>`, 'text/html')
return doc.querySelector('.container')
}
I make this helper function to return container with your HTML nodes. It will be need in the next step.
2. Transform DOM document into React JSX tree. Now that you have DOM tree it's very easy to convert it to JSX by creating individual React elements out of corresponding DOM nodes. This function needs to be recursive to process all levels of the DOM tree. Something like this will do:
function getJSX(root) {
return [...root.children].map(element => {
const children = element.children.length ? getJSX(element) : element.textContent
const props = [...element.attributes].reduce((prev, curr) => ({
...prev,
[curr.name]: curr.value
}), {})
return React.createElement(element.tagName, props, children)
})
}
This is enough to create JSX out of DOM. It could be used like this:
const JSX = getJSX(getDOM(htmlString))
3. Inject Gallery. Now you can improve JSX creation to inject Gallery into created JSX if element contains more then 1 image tag. I would pass inject function into getJSX as the second parameter. The only difference from above version would be is how children is calculated in gallery case:
if (element.querySelector('img + img') && injectGallery) {
const imageSources = [...element.querySelectorAll('img')].map(img => img.src)
children = injectGallery(imageSources)
} else {
children = element.children.length ? getJSX(element) : element.textContent
}
4. Create Gallery component. Now it's time to create Gallery component itself. This component will look like this:
import React from 'react'
import { func, string } from 'prop-types'
function getDOM (html) {
const parser = new DOMParser()
const doc = parser.parseFromString(`<div class="container">${html}</div>`, 'text/html')
return doc.querySelector('.container')
}
function getJSX(root, injectGallery) {
return [...root.children].map(element => {
let children
if (element.querySelector('img + img') && injectGallery) {
const imageSources = [...element.querySelectorAll('img')].map(img => img.src)
children = injectGallery(imageSources)
} else {
children = element.children.length ? getJSX(element) : element.textContent
}
const props = [...element.attributes].reduce((prev, curr) => ({
...prev,
[curr.name]: curr.value
}), {})
return React.createElement(element.tagName, props, children)
})
}
const HTMLContent = ({ content, injectGallery }) => getJSX(getDOM(content), injectGallery)
HTMLContent.propTypes = {
content: string.isRequired,
injectGallery: func.isRequired,
}
export default HTMLContent
5. Use it! Here is how you would use all together:
<HTMLContent
content={serverResponse}
injectGallery={(images) => (
<Gallery images={images} />
)}
/>
Here is the demo of this code above.
Demo: https://codesandbox.io/s/2w436j98n
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With