Make component with only innerHTML

I'd like to make a SolidJS component with only innerHTML. This is what I have currently, but I'd love to get rid of the <div>
<div innerHTML={sanitizeHtml(props.token.content)} />
<div innerHTML={sanitizeHtml(props.token.content)} />
The reason is that I'm getting "tokens" from my Markdown processor, which is like a h1, h2, etc. level splitting of the document. I'm displaying those tokens in a For component in Solid. The above, div based component works, but breaks CSS, so I cannot write sibling selectors, everything is now wrapped in a dummy div. Is there any possible way in Solid, which would allow me not to put those dummy divs? Ideally I'd love to do something like this, similar to <> and </>.
< innerHTML={sanitizeHtml(props.token.content)} />
< innerHTML={sanitizeHtml(props.token.content)} />
Is this possible to do something like this in Solid? The reason I'm using a for loop over a Markdown token list is fine-grained reactivity, I only want to update the last part of the document, as the streaming Markdown comes in.
18 Replies
Madaxen86
Madaxen863mo ago
I think the only way for this is to append the content to a root container the good old vanilla style e.g.
const content = [
"<div id='content'>some content</div>",
"<span>some more content</span>",
];

function Content(props: any) {
onMount(() => {
// document.getElementById("md")!.innerHTML += props.content
//seems to be faster accoding https://stackoverflow.com/questions/7327056/appending-html-string-to-the-dom$0
document.getElementById("md")!.insertAdjacentHTML("beforeend", props.content)
});
return <></>;
}

function Container() {
return (
<div id="md">
<For each={content}>{(item) => <Content content={item} />}</For>
</div>
);
}
const content = [
"<div id='content'>some content</div>",
"<span>some more content</span>",
];

function Content(props: any) {
onMount(() => {
// document.getElementById("md")!.innerHTML += props.content
//seems to be faster accoding https://stackoverflow.com/questions/7327056/appending-html-string-to-the-dom$0
document.getElementById("md")!.insertAdjacentHTML("beforeend", props.content)
});
return <></>;
}

function Container() {
return (
<div id="md">
<For each={content}>{(item) => <Content content={item} />}</For>
</div>
);
}
https://playground.solidjs.com/anonymous/9a86b9fd-7504-479e-b334-854487a245f8
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
hyperknot
hyperknotOP3mo ago
But this way I loose fine-grained-reactivity, don't I? This way it's basically not Solid anymore, just a direct DOM manipulation.
thetarnav
thetarnav3mo ago
it will also cause the whole markdown elements to be recreated with each change you could try to render html to individual divs like before but then take its children and append them to md root instead
Madaxen86
Madaxen863mo ago
Little correction: moved the Root “md” atop the for loop and doesn’t wrap it anymore: https://playground.solidjs.com/anonymous/c7baa8a6-cac6-4218-a7e3-3f3205d1f471 I don’t see elements to be recreated if elements are appended to content. Haven’t tested with inserting elements in the middle.
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
hyperknot
hyperknotOP3mo ago
But does it handle the last HTML string changing over time? So a streaming markdown document comes in, it'll look something like this:
["<div id='content'>some</div>"]
["<div id='content'>some</div>"]
->
["<div id='content'>some content</div>"]
["<div id='content'>some content</div>"]
->
[
"<div id='content'>some content</div>",
"<span>some</span>",
];
[
"<div id='content'>some content</div>",
"<span>some</span>",
];
->
[
"<div id='content'>some content</div>",
"<span>some more</span>",
];
[
"<div id='content'>some content</div>",
"<span>some more</span>",
];
->
[
"<div id='content'>some content</div>",
"<span>some more content</span>",
];
[
"<div id='content'>some content</div>",
"<span>some more content</span>",
];
The current solution takes care of it with <For> and reconcile, only modifying the actually changed part. If I manually append then I think it'll be duplicated.
Madaxen86
Madaxen863mo ago
👆true It would probably make sense to use something like markdown-it to convert the markdown to structured data and then render the elements with Dynamic.
hyperknot
hyperknotOP3mo ago
I'm using marked to conver the markdown to HTML, splitting it into "tokens". What would markdown-it offer which would help me? How can I use Dynamic for this?
thetarnav
thetarnav3mo ago
instead of transforming md to html transform it to ast (structured data) which can be rendered with solid like any other data Diffing it with Index instead of For probably
thetarnav
thetarnav3mo ago
also fwiw I’ve make a library specifically for rendering streamed markdown: https://github.com/thetarnav/streaming-markdown
GitHub
GitHub - thetarnav/streaming-markdown: 🔴Ⓜ️⬇️ Streaming m...
🔴Ⓜ️⬇️ Streaming markdown à la ChatGPT (WIP). Contribute to thetarnav/streaming-markdown development by creating an account on GitHub.
hyperknot
hyperknotOP3mo ago
Nice one, what's the chance! Also the demo is really nice on the homepage. But once you have the AST, how do you render it in Solid? At one point you have the same problem as I, when you need to make something like innerHTML on a root level of a component. I think my problem could be solved by getting one level deeper and calling DOM Expressions directly, but I never used it.
thetarnav
thetarnav3mo ago
no you just readers ast like any other data without using innerHtml md “ast” should give you a tree of tokens with their type and additional attributes which should be easily renderable with some recursive component
hyperknot
hyperknotOP3mo ago
You mean the whole tree gets in one component, without using <For>, right? But then you have to reimplement the For's built-in diffing. But I have a feeling DOM Expressions is made for exactly this, I just don't know how do use it 🙂
Madaxen86
Madaxen863mo ago
You’d pass the tree into the For/Index and then render each element (recursively) until you hit the "innerText".
hyperknot
hyperknotOP3mo ago
I think I need to look into how does For work internally, and maybe I can modify it such that it doesn't need to return a wrapper.
bigmistqke
bigmistqke3mo ago
No he means: 1. Turn markdown into ast representing the html 2. Walk down that ast in a recursive component 2. Would be something like
function Comp(props){
const [,rest] = splitProps(props, ['children', 'kind'])
return <Dynamic element={props.kind} {...rest}>
<Index each={props.children}>
{(node) => <Comp {...props} />
</Index>
</Dynamic>
}
function Comp(props){
const [,rest] = splitProps(props, ['children', 'kind'])
return <Dynamic element={props.kind} {...rest}>
<Index each={props.children}>
{(node) => <Comp {...props} />
</Index>
</Dynamic>
}
hyperknot
hyperknotOP3mo ago
I don't yet see how would it do the diffing. There is no "key" on the AST I could use. The good thing on a 1-level deep array of HTML strings is that reconcile does a perfect work of diffing, so For can just render it. But maybe I could use Dynamic for this.
bigmistqke
bigmistqke3mo ago
Because in your usecase there is only new content concatenated at the end you don't have to diff at all: simply use <Index/> to iterate over the children The key then would be the child's index
hyperknot
hyperknotOP3mo ago
OK, that's true. And it should handle changes in the last child I believe.

Did you find this page helpful?