solid icon indicating copy to clipboard operation
solid copied to clipboard

Hydration error for rendered Elements that aren't inserted in the DOM during server rendering

Open GiyoMoon opened this issue 2 years ago • 6 comments

Describe the bug

SolidJS produces hydration errors when resolving children that aren't attached to the DOM.

Picture this:

const Component = (props) => {
  const resolvedChildren = children(() => props.children)
  return <Show when={false}>{resolvedChildren()}</Show>
}

Here, the children resolve but aren't attached to the actual DOM and it throws a hydration error.

Your Example Website or App

https://github.com/GiyoMoon/solid-hydration-error

Steps to Reproduce the Bug or Issue

  1. pnpm install
  2. pnpm dev
  3. go to localhost:3000
  4. see hydration error

Expected behavior

This shouldn't throw a hydation error.

Screenshots or Videos

No response

Platform

Solid 1.8.7 SolidStart 0.3.10

Additional context

No response

GiyoMoon avatar Dec 07 '23 19:12 GiyoMoon

I'm generalizing the title because this issue impacts more than just children. This may be the most obvious but other locations are when people double render things for conditionals. Generally those are poor practices because they cause a lot of extra work but this issue can be a marker this.

Things like:

<Show when={<RenderComponent />}>
  <RenderComponent />
</Show>

You should never ever ever do this. But I imagine there might be other patterns that we miss that are a bit more legit.

ryansolid avatar Dec 19 '23 16:12 ryansolid

I'm generalizing the title because this issue impacts more than just children. This may be the most obvious but other locations are when people double render things for conditionals. Generally those are poor practices because they cause a lot of extra work but this issue can be a marker this.

Things like:

<Show when={<RenderComponent />}>
  <RenderComponent />
</Show>

You should never ever ever do this. But I imagine there might be other patterns that we miss that are a bit more legit.

Hey @ryansolid, But then what would be the correct pattern? I would like to display a jsx element only if it exists though, conditionally, passed through a prop.

itsyoboieltr avatar Dec 19 '23 18:12 itsyoboieltr

Another solution that I'm currently using, feels very clean to me: (This probably also applies to other use cases, not only children)

const keepAlive = createMemo((prev) => prev || show(), false)

return (
  <Show when={keepAlive()}>
    {(() => {
      const resolvedChildren = children(() => props.children)
      return <Show when={show()}>{resolvedChildren()}</Show>
    })()}
  </Show>
)

It does render on the server if show() is true initially, which is what we want!

Credits go to depict_daniel on Discord, such a great solution!

GiyoMoon avatar Dec 19 '23 18:12 GiyoMoon

I want to clarify a bit because it might not be obvious to those reading this issue is we caused hydration errors to throw in in more cases post 1.8. Before 1.8 this would just kinda sneak by. We'd clone the nodes and not insert and it would probably work in a lot of cases, but we'd be creating DOM elements during hydration. I made a call with 1.8.1 to make things error more strictly because we no longer update DOM nodes during hydration either, so when people were getting things working by chance here other downstream changes were causing issues. The thinking was if we addressed this higher up with a straight up error it would be easier to track down. And it is.

However, I'm finding a lot of people were doing the double render pattern. Obviously it is not a good thing that they were but there is a question of how forgiving we should be of people doing these sort of footguns. I labelled this specific case as a bug becausethe user shouldn't ideally have to worry about this in a single render case. They are not abusing accessing the props multiple times. With proper use of the children helper it should resolve. The problem today is that even with children it can't find the DOM nodes so it will create and be missing attributes that aren't being written. The only solution for this is nesting the children call or lazily evaluating children in general so they won't be accessed until after hydration in cases when not inserted. Hence this is a 2.0 thing.

However even after 2.0 the double prop access is still a concern.

ryansolid avatar Jan 08 '24 17:01 ryansolid

I found that it's also possible to use the solid-primitives's <Ref> component here as well for doing whatever is needed with the element

gabrielmfern avatar Feb 02 '24 19:02 gabrielmfern