S
SolidJS7mo ago
edygar

Hydration mismatch

The following code ALWAYS raises hydration mismatch while using solid-start with SSR. But that's not the case when there's no SSR https://playground.solidjs.com/anonymous/eee9dea7-56ba-4e0d-a6b5-fb5e24c778e2
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
19 Replies
peerreynders
peerreynders7mo ago
Turn content into a component (render-prop?) and the problem goes away.
import { For, Match, Switch, createSignal } from 'solid-js';
import type { JSX } from 'solid-js';

const Content = (props: {
tabs: { name: JSX.Element; content: () => JSX.Element }[];
}) => {
const [currentTab, setCurrentTab] = createSignal(0);
return (
<>
<For each={props.tabs}>
{({ name }, i) => (
<button onClick={() => setCurrentTab(i())}>{name}</button>
)}
</For>
<Switch>
<For each={props.tabs}>
{({ name, content }, i) => (
<Match when={currentTab() === i()}>
<div>
<h1>{name}</h1>
{content()}
</div>
</Match>
)}
</For>
</Switch>
</>
);
};
export default function App() {
return <Content tabs={[{ name: 'test', content: () => <div>test</div> }]} />;
}
import { For, Match, Switch, createSignal } from 'solid-js';
import type { JSX } from 'solid-js';

const Content = (props: {
tabs: { name: JSX.Element; content: () => JSX.Element }[];
}) => {
const [currentTab, setCurrentTab] = createSignal(0);
return (
<>
<For each={props.tabs}>
{({ name }, i) => (
<button onClick={() => setCurrentTab(i())}>{name}</button>
)}
</For>
<Switch>
<For each={props.tabs}>
{({ name, content }, i) => (
<Match when={currentTab() === i()}>
<div>
<h1>{name}</h1>
{content()}
</div>
</Match>
)}
</For>
</Switch>
</>
);
};
export default function App() {
return <Content tabs={[{ name: 'test', content: () => <div>test</div> }]} />;
}
Some Call Me Tim
I'm actually surprised that works at all, I wouldn't have thought that <For> would be a valid direct chid of <Switch>
edygar
edygarOP7mo ago
It worked, but seems awful as an API, what would be the most solid way of doing that
peerreynders
peerreynders7mo ago
what would be the most solid way of doing that
4. Simple is better than easy Explicit and consistent conventions even if they require more effort are worth it.
Symptom: Missing hydration key Inspecting "View Source" reveals that the key is indeed missing while the rest of the markup is intact so it's not a case of the browser helpfully rearranging the DOM after rendering. Hypothesis: For some reason the hydration key doesn't stick. Possible mitigation: Delay JSX rendering until necessary hydration key can be applied-i.e. render prop. Possible mechanism: Components are designed to be nested, injection is an edge case. In case of SSR, JSX is rendered to string for optimal server-side performance, so augmentation of anything already rendered would require parsing that content and re-rendering it with the augmentation. Delaying rendering until the such time that the hydration key can be applied in the normal fashion is the most streamlined solution. As far as trade offs go seems pretty minor.
SolidJS
Solid is a purely reactive library. It was designed from the ground up with a reactive core. It's influenced by reactive principles developed by previous libraries.
edygar
edygarOP7mo ago
Thank you!
edygar
edygarOP7mo ago
3 Patterns to Write Better and More Readable SolidJS Components - R...
This post explains how to write more readable declarative components in solidjs
Some Call Me Tim
Some of those rub me the wrong way, lots of footguns. For one, there's no way to enforce the type of children. Like the tabs for example, I'd have the <Tab> component render its own content. Maybe it's just user preference though. One thing I do like is a setup i found ages ago... here's a snippet from a popup menu I made:
<PopupMenu>
<Switch>
<Match when={!somevariable}>
<Show when={!props.disableDuplicate}>
<PopupMenu.MenuItem onClick={handleDuplicateActionClick}>
Duplicate
</PopupMenu.MenuItem>
</Show>

<PopupMenu.MenuItem onClick={handleDeleteActionClick}>
Delete
</SBPopupMenu.MenuItem>
</Match>

<Match when={somevariable}>
<PopupMenu.MenuItem onClick={handleCloneActionClick}>
Clone
</SBPopupMenu.MenuItem>
</Match>
</Switch>
</PopupMenu>
<PopupMenu>
<Switch>
<Match when={!somevariable}>
<Show when={!props.disableDuplicate}>
<PopupMenu.MenuItem onClick={handleDuplicateActionClick}>
Duplicate
</PopupMenu.MenuItem>
</Show>

<PopupMenu.MenuItem onClick={handleDeleteActionClick}>
Delete
</SBPopupMenu.MenuItem>
</Match>

<Match when={somevariable}>
<PopupMenu.MenuItem onClick={handleCloneActionClick}>
Clone
</SBPopupMenu.MenuItem>
</Match>
</Switch>
</PopupMenu>
All it takes is a PopupMenu.MenuItem = PopupMenuItem; at the bottom of the PopupMenu.tsx file.
edygar
edygarOP7mo ago
But one possibility could be a context that the descents register using a createRenderEffect So it doesn’t really matter the “type”, only the the right ones would be able to affect
Some Call Me Tim
yup, could do a context too, but personally for something like tabs/popups or whatever, it'd be just more mental overhead with the 'oh did this register', 'did this unload' etc. I'd rather just have it verbose like above where the chlid is tied to the parent. The joys of flexibility 🙂 If context works better for your mental model, go nuts
jer3m01
jer3m017mo ago
I'm really not a fan of any of these. They all impact performance, awkwardly get around of solid's jsx and make your components less reusable
edygar
edygarOP7mo ago
GitHub
[Bug?]: Hydration Mismatch during Development. Nesting element with...
Duplicates I have searched the existing issues Latest version I have tested the latest version Current behavior 😯 Route throws consistently when accessing it during development. Production build se...
ryansolid
ryansolid7mo ago
The problem here is that even without SSR you are rendering the elements multiple times.
edygar
edygarOP7mo ago
@ryansolid I can see it happening now that you told me, I just can't understand why!
import { createMemo, type JSX } from "solid-js";

const ReproduceSteps = (props: {
test: { propertyBesidesElement: JSX.Element; element: JSX.Element };
}) => {
const test = createMemo(() => props.test);
return (
<button onClick={() => console.log(test().propertyBesidesElement)}>
{test().element}
</button>
);
};
export default function App() {
return (
<ReproduceSteps
test={{
propertyBesidesElement: "test",
element: (
<div
style={`background: #${Math.trunc(Math.random() * 0xffffff)
.toString(16)
.padStart(0)}`}
>
test
</div>
),
}}
/>
);
}
import { createMemo, type JSX } from "solid-js";

const ReproduceSteps = (props: {
test: { propertyBesidesElement: JSX.Element; element: JSX.Element };
}) => {
const test = createMemo(() => props.test);
return (
<button onClick={() => console.log(test().propertyBesidesElement)}>
{test().element}
</button>
);
};
export default function App() {
return (
<ReproduceSteps
test={{
propertyBesidesElement: "test",
element: (
<div
style={`background: #${Math.trunc(Math.random() * 0xffffff)
.toString(16)
.padStart(0)}`}
>
test
</div>
),
}}
/>
);
}
even with the createMemo
ryansolid
ryansolid7mo ago
Because it roughly compiles to:
ReproduceSteps({
get test() {
return {
propertyBesidesElement: "test",
element: createElement("div")
}
}
})
ReproduceSteps({
get test() {
return {
propertyBesidesElement: "test",
element: createElement("div")
}
}
})
So every time props.test is called it would create the Div again. That being said.. createMemo I'd expect to be fine here. I mean client-wise I can see createMemo only have it call once, but the question I guess is there still a mismatch there.
edygar
edygarOP7mo ago
The mismatch won't happen with the createMemo, but I'm still a bit confused, since it was refering once to the said property
peerreynders
peerreynders7mo ago
since it was referring once to the said property
The behaviour I'm observing here https://playground.solidjs.com/anonymous/a4d1a55b-58e2-48b9-b1de-64263f26556a suggests that a prop member is reified in its entirety the moment it is accessed. So the moment you access one property of test you are committing all of test. Fortunately with a memo that only happens once but both of them become DOM elements (strings on the server) even when only one of them is accessed.
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
edygar
edygarOP7mo ago
Ok, mine was just a bad example, the colors were changing due to server/client different random, but it was running through just once in each. @peerreynders's one is much more :solid: solide and createMemo solves any issues @ryansolid I was thinking, maybe there's an opportunity to add a lint rule, warning level, whenever a component expects a JSX.Element as property, to avoid reading more than once or to wrap on a memo
ryansolid
ryansolid5mo ago
That's great rule even without SSR in mind.. when you do that you render the actual DOM node twice.
Want results from more Discord servers?
Add your server