solidjs / solid

A declarative, efficient, and flexible JavaScript library for building user interfaces.
https://solidjs.com
MIT License
32.17k stars 918 forks source link

strange hydration mismatch #2120

Closed teidesu closed 5 months ago

teidesu commented 5 months ago

Describe the bug

in console there appears an error like this, hot reload stops working and any interactivity on the page doesn't work either:

Error: Hydration Mismatch. Unable to find DOM nodes for hydration key: 0-0-0-0-0-0-0-0
    at getNextElement (chunk-2LCSCBTA.js:740:13)
    at get items (+Page.tsx:14:25)
    at Object.fn (+Page.tsx:22:60)
    at runComputation (chunk-4LHGDXYW.js:809:22)
    at updateComputation (chunk-4LHGDXYW.js:788:3)
    at createMemo (chunk-4LHGDXYW.js:273:5)
    at TextTable2 (+Page.tsx:22:25)
    at @solid-refresh:25:42
    at untrack (chunk-4LHGDXYW.js:513:12)
    at HMRComp.createMemo.name [as fn] (@solid-refresh:25:28)

Your Example Website or App

https://stackblitz.com/edit/dj8qvo?file=pages%2Findex%2F%2BPage.tsx

Steps to Reproduce the Bug or Issue

see attached stackblitz repro

Expected behavior

as a user, i expected everything to work fine

Screenshots or Videos

No response

Platform

Additional context

the issue can be worked around if i remove the createMemo call, so i initially thought this is the same bug as #1485. but looking closer it might not be, since it can also be fixed by making the value field in the prop accept a function instead of a JSX.Element directly:

...
 <div>
-  {item.name}: {item.value}
+  {item.name}: {item.value()}
 </div>
...

as well as by removing the internal <b> tag in the value itself:

 value: (
   <>
-    foo <b>bar</b>
+    foo
   </>
 ),

which would probably mean that it's somehow related to the fact that jsx is constructed in the parent component. but then it's unclear how does removing createMemo fix this...

birkskyum commented 5 months ago

solid start example of same error

birkskyum commented 5 months ago

@teidesu , so the JSX should be returned in a function like this:

interface TextTableProps {
  items: {
    name: string;
    value: () => JSX.Element;
  }[];
}

and then the value can be set as:

value: () => (
              <>
                foo<div>div</div>
              </>
            ),

and used in a template as:

<div>
  {item.name}: {item.value()}
</div>
ryansolid commented 5 months ago

It's that you are recreating the items every time you access the prop. We evaluate expressions lazily. In so you are creating DOM elements that haven't been inserted in the DOM and when the client tries to find them they aren't there. So the error is correct and by design.

We recommend using children helper(https://docs.solidjs.com/reference/component-apis/children) if you wish to read children outside of the JSX and use them multiple places.

  const propItems = children(() => props.items).toArray();
  const maxNameLength = createMemo(() =>
    Math.max(...propItems.map((item) => item.name.length))
  );

  const items = () =>
    propItems.map((item) => (
      <div>
        {item.name}: {item.value}
      </div>
    ));

This will not have any issue. Alternatively you can have the JSX in the passed in template be created lazily itself by wrapping it in a function as suggested above.