I’m building a custom dropdown, where each option has an SVG icon and a label.
While the icon of the selected element is rendered initially, the SVG icon is removed upon expanding the select. Whenever a new option is selected, it’s rendered again until the select is expanded again. There’s no change to the value().icon
state upon expansion.
Within the JSX there’s no condition applied to the svg
in value().icon
either:
<button
onClick={() => setExpanded((prev) => !prev)}
>
<span>{value().icon}</span>
<span>{value().label}</span>
<svg />
</button>
Reproducible Example Playground
Based on browser debugging (breakpoint on node removal on the svg
), I believe something odd happens around dom-expressions L78. The value
in parent.appendChild(value)
seems to correctly contain the svg
on first selection (when it prevails), but also on expansion (when it’s removed), which I can’t make sense of.
2
Answers
Your list is wrapped into a
Show
component, whenever state is updated whole list will re-created because hiding will destroy the previously rendered element.If you check the output code, your component logic is complicated and svg part ends up tied to the
Show
element even though svg part is pure:You need to refactor your code in a way that state update does not trigger re-render for the svg. You can move your Select logic into smaller, well contained sub-components which provides proper isolation and can take advantage of memos and untracks in case state update spill onto them.
Solid does not use VDOM but compiles JSX into native DOM elements. The way it works is, Solid converts component’s html structure into a template. Whenever state gets updated, it clones this template, fills with with dynamic values by evaluating them, and re-insert it to its parent component.
You can write any expression inside JSX. Chlid components are functions and they are compiled into function calls.
If you take a look at the output code, you will see svg is compiled into
_tmpl$5
and it is inserted into its parent underShow
:This means, whenever
expanded
value changes the children ofShow
component will be re-created and re-inserted.Normally you don’t expect svg ends up being a child to
Show
because it comes before theShow
in the DOM hierarchy and appears outside of it. Your component logic unnecessary complex and convoluted causing some unexpected outcome, a it is pointed in the accepted answer.Don’t rush for returning an element, take you time, build your logic, tame your state, only then return the element.
Here is an
Select
demo I wrote for another answer which might be helpful. It has basic functionality but can be improved easily:https://playground.solidjs.com/anonymous/e58974e7-287f-4f56-8ab3-33787d93c629
Firstly, I would copy the code from the playground to that it’s reproducible in the future, but I added some comments in the code.
In SolidJS, when you do for example
const par = <p>hello</p>
for example,par
will refer to an actual DOM element (unlike in React which uses a virtual DOM node).So, the restrictions of a real DOM node applies. For example, if you try appending a node to multiple parents,
parent1.appendChild(node); parent2.appendChild(node)
, the child node is not cloned, but simply moved to parent2. So, parent1 will not have the child because the child goes with parent2.In each call to
App()
, for each color there is only one<Icon/>
instance.So effectively, when you show the options, what happens is that you have one DOM node that it tries to append to two different positions. But then the node can only appear in at most one place (because the node has at most one parent).
A workaround is to not use a single element like
icon?: JSXElement
, but rather to useicon?: () => JSXElement
which will generate separate elements as many times as it is called, along with appropriate changes in other places (e.g.icon: () => <Icon color="#fc5614" />
in App and<span>{value().icon?.()}</span>
in Select).This restriction doesn’t apply to strings like
value().value
, probably because it is only converted to an actual DOM node much later (unlike JSX tags which are converted to actual DOM elements very soon in SolidJS).The restriction also doesn’t seem to apply to React, probably because it converts the virtual DOM nodes to real DOM pretty late (so something like
2{child}3{child}4{child}
will not give you a weird behavior in React even when the child is a JSXElement, but it can be quite weird in SolidJS).