solid-start icon indicating copy to clipboard operation
solid-start copied to clipboard

[Bug?]: Hydration Mismatch for passed JSX elements

Open illiaChaban opened this issue 1 year ago • 2 comments

Duplicates

  • [X] I have searched the existing issues

Latest version

  • [X] I have tested the latest version

Current behavior 😯

When i pass jsx elements as a prop to my separation component I get hydration errors

export default function Home() {
  return (
    <main class="text-white">
      {/* works --> */}
      <Separate items={['hello 1']} />
      <Separate items={['hello 1', 'hello 2']} />
      {/* doesn't work --> */}
      <Separate items={[<span>Hello</span>, <span>world</span>]} />
    </main>
  );
}

const Separate = (p: { items: JSX.Element[] }) => {
  return (
    <p>
      <For each={p.items}>
        {(item, i) =>
          i() === p.items.length - 1 ? <>{item}</> : <>{item} • </>
        }
      </For>
    </p>
  );
};

Expected behavior 🤔

it should work without hydration errors "Hydration Mismatch. Unable to find DOM nodes for hydration key ..."

Steps to reproduce 🕹

Stackblitz link

Steps:

  1. Use this component:
export default function Home() {
  return (
    <main class="text-white">
      {/* works --> */}
      <Separate items={['hello 1']} />
      <Separate items={['hello 1', 'hello 2']} />
      {/* doesn't work --> */}
      <Separate items={[<span>Hello</span>, <span>world</span>]} />
    </main>
  );
}

const Separate = (p: { items: JSX.Element[] }) => {
  return (
    <p>
      <For each={p.items}>
        {(item, i) =>
          i() === p.items.length - 1 ? <>{item}</> : <>{item} • </>
        }
      </For>
    </p>
  );
};

Context 🔦

error "Hydration Mismatch. Unable to find DOM nodes for hydration key: 0-0-0-0-0-0-0-0-1-0-0-0-0-0-0-0-0-0-0-3-1-2"

Your environment 🌎

"dependencies": {
    "@solidjs/router": "^0.13.3",
    "@solidjs/start": "^1.0.2",
    "autoprefixer": "^10.4.19",
    "postcss": "^8.4.38",
    "solid-js": "^1.8.17",
    "tailwindcss": "^3.4.3",
    "vinxi": "^0.3.12"
  },
  "engines": {
    "node": ">=18"
  },
  "devDependencies": {
    "sass": "^1.77.6"
  }

illiaChaban avatar Jul 01 '24 00:07 illiaChaban

Update: if i pass a function that returns JSXElement instead of JSXElement instead, it works Although i don't think it's a fully expected behavior and I would still expect passing JSXElements to work i.e

<Separate items={[() => <span>Hello</span>, () => <span>world</span>]} />

illiaChaban avatar Jul 01 '24 00:07 illiaChaban

This is expected with current state of hydration.

<Separate items={[<span>Hello</span>, <span>world</span>]} />

compiles to

_$createComponent(Separate, {
  get items() {
    return [_tmpl$(), _tmpl$2()];
  }
})

and accessing p.items generates new elements. as far as the hydration is concerned, there is nothing wrong with that as long as these elements can be found in the DOM.

👉 But new elements created while evaluating p.items.length won't exist in the dom hence the hydration error.

i() === p.items.length - 1 ? <>{item}</> : <>{item} • </>

this doesn't happen with

<Separate items={[() => <span>Hello</span>, () => <span>world</span>]} />

or

/* 🛑 use at your own risk */
const items = [<span>Hello</span>, <span>world</span>]
<Separate items={items} /> // no getter

or

function Separate(p) {
	/* ❌ don't do this */
	const items = p.items // just for the sake of the point

	const slightlyCorrect = createMemo(() => p.items)

	/* ✅ do this */
	const correct = children(() => p.items)
}

mdynnl avatar Jul 03 '24 13:07 mdynnl

@mdynnl how is this expected? From the DX point of view it doesn't make much sense and this behavior is not documented anywhere. Thanks for explaining on why it doesn't work due to some internal implementation details, however, IMO this is not an expected behavior, since i would expect SSR and SPA versions of Solid to work the same way

illiaChaban avatar Jul 06 '24 22:07 illiaChaban

why can't i generate a new elements and not append them to the DOM? Why can't they be hydrated as part of the javascript?

illiaChaban avatar Jul 06 '24 22:07 illiaChaban

Related Issue

doeixd avatar Jul 08 '24 12:07 doeixd

Yeah.. this is hard in general. The code above would be not good even in a purely client side app.. recreating elements over and over again. I'm going to close this as a duplicate of the linked issue in Solid core repo. But in general don't do this. Use the children helper if you are going to access a JSX rendering prop more than once.

ryansolid avatar Sep 25 '24 21:09 ryansolid