Search code examples
javascripthtmlsolid-js

Import and render an html element with SolidJS


When my application loads, it will call the backend to obtain a description of components the user decides to add.

These objects should have a function to generate their html, take a button for example: it will export an html() method that returns a string containing an HTML text:

const buttonText = "I'm a button"
export default {
  html() {
    return `<button>${buttonText}</button>`
  }
}

On the frontend side, I will be using import statement to load the aforementioned button module and store it in a JSON object, sort of like a plugins manager.

When it comes time to render it, I tried using Dynamic:

<For each={plugins()} fallback={<p>Loading...</p>}>{ plugin =>
  <div>
    <Dynamic component={plugin.module.html()}>
    </Dynamic>
  <div>
}</For>

But it falls with DOMException: Failed to execute 'createElement' on 'Document': The tag name provided ('<button>I'm a button</button>') is not a valid name.

Which makes sense since it is expecting something like a "div" element, not a string like <button>I'm a button</button>.

What would be the correct way to render HTML strings in SolidJS?


Solution

  • A simple solution would be just to use innerHTML like so:

    <For each={plugins()} fallback={<p>Loading...</p>}>
    { plugin =>
     <div innerHTML={plugin.module.html()}>
     <div>
    }</For>
    

    If you need something more interactive, perhaps using template and cloneNode which is what Solidjs compiles your code to normally. Here is a full example below. I created it on the playground.

    import { render, template } from "solid-js/web";
    import { For } from "solid-js";
    
    function MakeButton() {
      const plugins = ["<button>Click me</button>"];
    
      const handleClick = () => {
        console.log("Clicked button");
      }
    
      const createElem = (el: string) => {
        const elem = template(el, 2);
        const ret = elem.cloneNode(true);
        if (ret.tagName === "BUTTON") {
          ret.onclick = handleClick;
        }
        return ret;
      }
    
      return (<>
        <For each={plugins} fallback={<div>Loading...</div>}>
          {plugin =>
            <div>{createElem(plugin)}</div>
          }
        </For>
      </>);
    }
    
    render(() => <MakeButton />, document.getElementById("app"));