3

I would like to set JSX tag names dynamically in SolidJS. I come from React where it is fairly simple to do:

/* Working ReactJS Code: */
export default MyWrapper = ({ children, ..attributes }) => {
  const Element = "div";

  return (
    <Element {...attributes}>
      {children}
    </Element>
  )
}

but when I try to do the same thing in SolidJS, I get the following error:

/* Console output when trying to do the same in SolidJS: */
dev.js:530 Uncaught (in promise) TypeError: Comp is not a function
  at dev.js:530:12
  at untrack (dev.js:436:12)
  at Object.fn (dev.js:526:37)
  at runComputation (dev.js:706:22)
  at updateComputation (dev.js:691:3)
  at devComponent (dev.js:537:3)
  at createComponent (dev.js:1236:10)
  at get children [as children] (Input.jsx:38:5)
  at _Hot$$Label (Input.jsx:7:24)
  at @solid-refresh:10:42

I would like to know if I miss something here, or whether it is possible to achieve this in SolidJS in any other way.

Norbert Biró
  • 636
  • 7
  • 16

2 Answers2

7

Solid has a <Dynamic> helper component for that use.

import { Dynamic } from "solid-js/web";

<Dynamic component="div" {...attributes}>
   {props.children}
</Dynamic>
thetarnav
  • 788
  • 2
  • 8
  • Correct link to the document is [this](https://www.solidjs.com/docs/latest/api#dynamic). Due to "too many pending edits" ([explanation](https://meta.stackexchange.com/questions/84362/why-does-the-suggested-edit-queue-have-a-fixed-size-and-what-is-its-size-on-eac)) I couldn't edit the post. – ozanmuyes Mar 16 '23 at 23:59
  • Why on earth is this part of `solid-js/web` – David Callanan May 19 '23 at 03:37
  • @DavidCallanan I believe this is because `Dynamic` is a platform-specific component. If you pass `"div"` as the tag, it has to render it and apply attributes. So if you were to make a custom renderer for three.js or mobile, you'd have to implement your own `Dynamic` component too. – thetarnav May 22 '23 at 20:25
  • @thetarnav Ah ok. I wonder then if there is any fundamental feature that `Dynamic` is implemented on top of, that allows for the desired functionality. – David Callanan May 24 '23 at 03:25
0

Here is an alternative implementation covering simple cases like strings and nodes although you can extend it to cover any JSX element:

import { Component, JSXElement} from 'solid-js';
import { render,  } from 'solid-js/web';

const Dynamic: Component<{ tag: string, children: string | Node }> = (props) => {
  const el = document.createElement(props.tag);

  createEffect(() => {
    if(typeof props.children === 'string') {
      el.innerText = String(props.children);
    } else if (props.children instanceof Node){
      el.appendChild(props.children);
    } else {
      throw Error('Not implemented');
    }
  });

  return el;
};

const App = () => {
  return (
    <div>
      <Dynamic tag="h2">This is an H2!</Dynamic>
      <Dynamic tag="p">This is a paragraph!</Dynamic>
      <Dynamic tag="div"><div>Some div element rendering another div</div></Dynamic>
    </div>
  )
}

render(App, document.body);

This works because Solid components are compiled into native DOM elements, however since we do not escape the output, it is dangerous to render any children directly, given that you have no control over the content.

This alternative comes handy when you need to render rich text from a content editable or a textarea, text that includes tags like em, strong etc. Just make sure you use innerHTML attribute instead of innerText.

snnsnn
  • 10,486
  • 4
  • 39
  • 44